A jednak speed x 5



Dało się przyspieszyć bez uciekania się do mod_perla. Zamiast odpytywać bazę o każdą komórkę, zrobiłem jedno zapytanie wyciągające dane na cały tydzień do hasha, w pętli operując już tylko na hashu. Całość dostała kopa niesamowitego.
Cały czas mowa o tym Systemie Rezerwacji. Beware: ten link prowadzi nie bezpośrednio do systemu, ale do jego uproszczonej wersji. Wersja właściwa znajduje się na vhoście niedostępnym spoza naszej sieci akademickiej. Dlatego to, co można obejrzeć z internetu, pozbawione jest kilku arkuszy styli i możliwości wejścia głębiej - można zobaczyć tylko spis. Wystarcza to jednak do ocenienia liczbie zapytań do bazy, które teraz zastąpione są jednym.


Przykład optymalizacji zapytań SQL:

Problem jest taki: mamy w bazie kolejne pola. Problemem jest to, ze numeracja mimo, że sekwencyjna, jest nieciagla. Za zadanie mamy odnalezienie poprzedniego pola w stosunku do $DANE, przy czym nie ma byc on bardziej odległe niż 168 numerków do danego.

  • Pierwsze podejście
  • $akt_nr = $DANE-1;
    do {
                    $query = "SELECT pole FROM tabelka WHERE numer='$akt_nr'";
                    $result = $dbh->prepare($query);
                    $result->execute;
                    $akt_gnr--;
    } while (!(@row = $result->fetchrow) && $akt_nr > $DANE-168);
    

    Zostanie wykonane maksymalnie 168 zapytań do bazy. Jest duża szansa, że przepytywanie skończy się szybciej, zawsze jednak będzie to conajmniej jedno zapytanie.<

  • Drugie podejście - niech to zrobi za nas baza
  • Zamiast samemu przetwarzać otrzymane dane, właściwym podejściem jest zrzucenie jak największej ilości pracy na bazę danych. Musimy skonkretyzować nasze żądania:

    $query = "SELECT pole FROM tabelka WHERE numer<$DANE AND numer>$DANE-168 ORDER BY numer DESC LIMIT 1;";
    $result = $dbh->prepare($query);
    $result->execute;
    @row = $result->fetchrow;
    

    I już! Czym różni się to zapytanie od poprzedniego? Po pierwsze, sprawdzanie warunku $akt_nr > $DANE-168 zastąpione zastało zawężeniem zakresu, z którego spodziewamy się wyników od bazy. Po drugie, życzymy sobie posortowania wyników wg. numeru, w kolejności malejącej (DESC). Dlaczego? Ponieważ wtedy pierwszym zwróconym wynikiem będzie ten o najwyższym numerze, za nim będzie o numerze mniejszym, potem jeszcze mniejszym itd. Ponieważ interesuje nas najpóźniejszy wpis -- który zostanie zwrócony jako pierwszy -- ograniczmy się tylko do pierwszego wyniku poprzez LIMIT 1. Pozostałe nie będą przepychane przez kanał komunikacyjny program<->baza. Po co nam one, skoro i tak mają iść od razu do śmieci?

  • Problem trzeci - ciąg danych
  • Tutaj zadanie jest takie: mamy do dyspozycji pewien zakres (od $BEG do $END) numerów, na których będziemy operować. Numery, choć dyskretne, są nieciągłe -- niektórych brakuje. Do przeprowadzenia mamy parę operacji na poszczególnych numerkach. Co robimy?
    O każdy numer można pytać się w momencie, kiedy jest potrzebny:

    pętla {
           [...]
           $query = "SELECT pole FROM tabelka WHERE numer=$potrzebny;";
           $result = $dbh->prepare($query);
           $result->execute;
           @row = $result->fetchrow;
           if (defined($row[0])) { zrob_cos_z($row[0])};
    }
    

    Takich zapytań będzie $END - $BEG, co czasem daje sporo odwołań do bazy. Kosztownych czasowo, zwłaszcza, gdy pobierane pole sklada się np. z numerka i jakieś małej literki.
    Rozwiązaniem jest operowanie na większych porcjach danych pobieranych z bazy:

    $query = "SELECT pole FROM tabelka WHERE numer>$BEG-1 AND numer<$END+1;
    $result = $dbh->prepare($query);
    $result->execute;
    $hashref = $result->fetchall_hashref("numer");
    
    pętla {
          [...]
          $nr = %$hashref->{$potrzebny}->{"costam"};
          if (defined($nr)) { zrob_cos_z($nr); };
    };
    

    W ten sposób do bazy odwołujemy się raz, a nie za każdym przebiegiem pętli. Wyniki otrzymujemy w hashu, można się do nich łatwo odwoływać przez poszukiwany numer. Minusem jest zwiększone wykorzystanie pamięci przez naszą aplikację, jednak przyrost szybkości prawie zawsze rekompensuje tą niedogodność.

    Mając do wykonania większość ilość iteracji podobnych do tych w Przykładzie pierwszym powyżej, należy się zastanowić -- czy dla każdego wyszukiwania generować SELECT z odpowiednio zawężonym zakresem i LIMIT 1? A może lepiej pobrać cały interesujący nas przedział za jednym zapytaniem i używać pętli takiej jak w pierwszym kawałku kodu, zastępując jednak odpytywanie bazy -- odwołaniami do poszczególnych pozycji hasha.


    Archived comments:

    jpc 2004-04-08 16:23:33

    Nie tak źle, tylko czemu Perl a nie Python? :)
    Use Webware (bedzie duzo szybszy).

    Comments


    Comments powered by Disqus