Czy klauzule GDZIE są stosowane w kolejności, w jakiej zostały zapisane?

36

Próbuję zoptymalizować zapytanie, które wygląda na dużą tabelę (37 milionów wierszy) i mam pytanie, w jakiej kolejności operacje są wykonywane w zapytaniu.

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

Czy WHEREklauzule zakresu dat są wykonywane przed podzapytaniem? Czy to dobry sposób na umieszczenie najbardziej restrykcyjnych klauzul na pierwszym miejscu, aby uniknąć dużych pętli dla innych klauzul, w celu szybszego wykonania?

Teraz wykonywanie zapytań zajmuje tyle czasu.

Jorge Vega Sánchez
źródło

Odpowiedzi:

68

Aby rozwinąć odpowiedź na @ alci:

PostgreSQL nie obchodzi, w jakiej kolejności piszesz rzeczy

  • PostgreSQL nie przejmuje się w ogóle kolejnością wpisów w WHEREklauzuli i wybiera indeksy oraz kolejność wykonania na podstawie samego oszacowania kosztów i selektywności.

  • Kolejność zapisywania złączeń jest również ignorowana aż do skonfigurowanej join_collapse_limit; jeśli jest więcej sprzężeń, wykona je w kolejności, w jakiej zostały zapisane.

  • Podzapytania mogą być wykonywane przed lub po zapytaniu, które je zawiera, w zależności od tego, co jest najszybsze, pod warunkiem, że podzapytanie jest wykonywane, zanim zapytanie zewnętrzne faktycznie potrzebuje informacji. Często w rzeczywistości podzapytanie jest wykonywane w środku lub przeplatane z zewnętrznym zapytaniem.

  • Nie ma gwarancji, że PostgreSQL w ogóle wykona części zapytania. Można je całkowicie zoptymalizować. Jest to ważne, jeśli wywołujesz funkcje z efektami ubocznymi.

PostgreSQL przekształci twoje zapytanie

PostgreSQL mocno przekształci zapytania, zachowując dokładnie te same efekty, aby przyspieszyć ich działanie bez zmiany wyników.

  • Warunki poza podzapytaniem mogą zostać zepchnięte do podzapytania, dzięki czemu są wykonywane w ramach podzapytania, a nie tam, gdzie zostały zapisane w zapytaniu zewnętrznym

  • Warunki w podzapytaniu można wyciągnąć do zapytania zewnętrznego, aby ich wykonanie odbywało się w ramach zapytania zewnętrznego, a nie tam, gdzie je napisano w podzapytaniu

  • Podzapytanie może i jest często spłaszczone w złączenie na zewnętrznym stole. To samo dotyczy rzeczy takich jak zapytania EXISTSi NOT EXISTSzapytania.

  • Widoki zostają spłaszczone w zapytaniu, które korzysta z widoku

  • Funkcje SQL często są wprowadzane do zapytania wywołującego

  • ... i do zapytań wprowadzono wiele innych transformacji, takich jak ciągła wstępna ocena wyrażeń, dekorelacja niektórych podkwerend i wszelkiego rodzaju inne sztuczki planowania / optymalizacji.

Ogólnie PostgreSQL może masowo przekształcić i przepisać zapytanie, do momentu, w którym każde z tych zapytań:

select my_table.*
from my_table
left join other_table on (my_table.id = other_table.my_table_id)
where other_table.id is null;

select *
from my_table
where not exists (
  select 1
  from other_table
  where other_table.my_table_id = my_table.id
);

select *
from my_table
where my_table.id not in (
  select my_table_id
  from other_table
  where my_table_id is not null
);

zazwyczaj wszystkie generują dokładnie ten sam plan zapytań. (Zakładając, że i tak nie popełniłem żadnych głupich błędów).

Często zdarza się, że próbujesz zoptymalizować zapytanie, ale okazuje się, że planista zapytań już wymyślił sztuczki, które próbujesz, i zastosował je automatycznie, więc wersja zoptymalizowana ręcznie nie jest lepsza od oryginalnej.

Ograniczenia

Planista / optymalizator nie jest wszechobecny i jest ograniczony wymogiem absolutnej pewności, że nie może zmienić efektów zapytania, dostępnych danych do podjęcia decyzji, wdrożonych reguł i czasu procesora stać go na zastanawianie się nad optymalizacjami. Na przykład:

  • Planista opiera się na statystykach prowadzonych przez ANALYZE(zwykle poprzez autovacuum). Jeśli są nieaktualne, wybór planu może być zły.

  • Statystyki są tylko próbką, więc mogą wprowadzać w błąd ze względu na efekty próbkowania, zwłaszcza jeśli pobierana jest zbyt mała próbka. Może to spowodować złe wybory planu.

  • Statystyki nie śledzą niektórych rodzajów danych o tabeli, takich jak korelacje między kolumnami. Może to prowadzić planistę do podejmowania złych decyzji, gdy zakłada, że ​​rzeczy są niezależne, a nie są.

  • Planista polega na parametrach kosztów, takich jak random_page_costokreślenie względnej prędkości różnych operacji w konkretnym systemie, na którym jest zainstalowany. To są tylko przewodniki. Jeśli są bardzo w błędzie, mogą prowadzić do złych wyborów planu.

  • Wszelkie podzapytania z LIMITlub OFFSETnie mogą być spłaszczone ani podlegać pullup / pushdown. To nie znaczy, będzie to wykonać przed wszystkimi częściami zewnętrznej kwerendy, chociaż, albo nawet, że będzie to wykonać w ogóle .

  • Warunki CTE (klauzule w WITHzapytaniu) są zawsze wykonywane w całości, jeśli w ogóle są wykonywane. Nie można ich spłaszczyć, a terminów nie można przesuwać w górę ani w dół przez barierę terminów CTE. Warunki CTE są zawsze wykonywane przed ostatnim zapytaniem. Jest to zachowanie niestandardowe dla SQL , ale jest udokumentowane jako działanie PostgreSQL.

  • PostgreSQL ma ograniczoną zdolność do optymalizacji zapytań dotyczących obcych tabel, security_barrierwidoków i niektórych innych specjalnych rodzajów relacji

  • PostgreSQL nie wstawi funkcji napisanej w niczym innym niż zwykły SQL, ani też pullup / pushdown między nią a zewnętrznym zapytaniem.

  • Planista / optymalizator jest naprawdę głupi w kwestii wybierania indeksów wyrażeń i trywialnych różnic typów danych między indeksem a wyrażeniami.

Mnóstwo też.

Twoje zapytanie

W przypadku zapytania:

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

nic nie powstrzymuje go przed spłaszczeniem w prostsze zapytanie z dodatkowym zestawem sprzężeń, i najprawdopodobniej tak będzie.

Prawdopodobnie okaże się coś takiego (oczywiście nie przetestowane):

select 1 
from workdays day
inner join offer on day.offer_id = offer.offer_day
inner join province on offer.id_province = province.id_province  
inner join center cr on cr.id_cr = province.id_cr 
where upper(offer.code_status) <> 'A' 
   and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
   and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
   and day.date_day >= '2014-10-01' 
   and day.date_day <= '2015-09-30';

PostgreSQL zoptymalizuje następnie kolejność łączenia i metody łączenia na podstawie swojej selektywności i oszacowań liczby wierszy oraz dostępnych indeksów. Jeśli te w rozsądny sposób odzwierciedlają rzeczywistość, dokona łączenia i uruchomi wpisy klauzuli where w dowolnej kolejności - często mieszając je razem, więc robi to trochę, potem trochę, a następnie wraca do pierwszej części itd.

Jak zobaczyć, co zrobił optymalizator

Nie można zobaczyć kodu SQL, do którego PostgreSQL optymalizuje zapytanie, ponieważ konwertuje ono SQL na wewnętrzną reprezentację drzewa zapytań, a następnie je modyfikuje. Państwo może zrzucić planu kwerend i porównać go z innymi zapytaniami.

Nie ma sposobu, aby „usunąć” ten plan zapytań lub wewnętrzne drzewo planu z powrotem do SQL.

http://explain.depesz.com/ ma pomocnika porządnego planu zapytań. Jeśli jesteś zupełnie nowy w wyszukiwaniu planów itp. (W tym przypadku jestem zaskoczony, że dotarłeś tak daleko w tym poście), to PgAdmin ma graficzną przeglądarkę planów zapytań, która zapewnia znacznie mniej informacji, ale jest prostsza.

Powiązana lektura:

Funkcje wypychania / podciągania i spłaszczania stale się poprawiają w każdym wydaniu . PostgreSQL zwykle ma rację co do decyzji podciągania / pchania / spłaszczania, ale nie zawsze, więc czasami musisz (ab) użyć CTE lub OFFSET 0hacka. Jeśli znajdziesz taki przypadek, zgłoś błąd w narzędziu do planowania zapytań.


Jeśli jesteś naprawdę bardzo zainteresowany, możesz również skorzystać z debug_print_plansopcji, aby zobaczyć plan zapytań, ale obiecuję, że nie chcesz tego czytać. Naprawdę.

Craig Ringer
źródło
Wow ... dość kompletna odpowiedź :-) Jednym z przypadków, w których miałem powolne plany z Postgresql (jak również z innymi dobrze znanymi silnikami DB, takimi jak Oracle), są korelacje między kolumnami lub wiele skorelowanych połączeń. Często kończy się to tworzeniem zagnieżdżonych pętli, myśląc, że w tym momencie planu jest tylko kilka rzędów, podczas gdy w rzeczywistości jest ich wiele tysięcy. Jednym ze sposobów optymalizacji tego rodzaju zapytań jest „ustawienie enable_nestloop = off;” na czas trwania zapytania.
alci
Natrafiłem na sytuację, w której wersja 9.5.5 próbowała zastosować TO_DATE przed sprawdzeniem, czy można ją zastosować, w prostej kwerendzie 7, gdzie zapytanie klauzula. Zamówienie miało znaczenie.
user1133275,
@ user1133275 W takim przypadku zadziałało to tylko przypadkowo, ponieważ szacunki kosztów obliczeń były takie same. PostgreSQL może nadal zdecydować się na uruchomienie to_dateprzed odprawą w późniejszej wersji lub z powodu zmiany statystyk optymalizatora. Aby niezawodnie uruchomić sprawdzanie przed funkcją, która powinna zostać uruchomiona dopiero po sprawdzeniu, użyj CASEinstrukcji.
Craig Ringer,
jedna z najlepszych odpowiedzi, jakie kiedykolwiek widziałem na SO! Kciuki w górę, stary!
62mkv,
Wystąpiły sytuacje, w których dodanie prostego zapytania order byspowodowało, że wykonanie zapytania było znacznie szybsze, niż gdyby nie było order by. To jeden z powodów, dla których piszę zapytania z łączeniami w taki sposób, jak gdybym chciał je wykonać - fajnie jest mieć świetnego optymalizatora, ale myślę, że nie jest rozsądne polegać całkowicie na swoim wyniku i pisać zapytania, nie zastanawiając się, jak to zrobić. on may bewykonany ... świetna odpowiedź !!
Greg0ry
17

SQL jest językiem deklaratywnym: mówisz, co chcesz, a nie jak to zrobić. RDBMS wybierze sposób wykonania zapytania, zwany planem wykonania.

Dawno, dawno temu (5–10 lat temu) sposób, w jaki zostało napisane zapytanie, miał bezpośredni wpływ na plan wykonania, ale obecnie większość silników baz danych SQL używa do planowania optymalizatora kosztowego. Oznacza to, że oceni różne strategie wykonania zapytania na podstawie jego statystyk dotyczących obiektów bazy danych i wybierze najlepszą.

Przez większość czasu jest naprawdę najlepszy, ale czasami silnik DB dokonuje złych wyborów, co powoduje bardzo wolne zapytania.

alci
źródło
Należy zauważyć, że w niektórych zapytaniach RDBMS kolejność zapytań jest nadal znacząca, ale w przypadku bardziej zaawansowanych wszystko, co mówisz, jest prawdą zarówno w praktyce, jak i teorii. Gdy planista zapytań wybiera zły wybór kolejności wykonania, zwykle są dostępne wskazówki dotyczące zapytań, które pozwalają popchnąć go w bardziej efektywnym kierunku (np. WITH(INDEX(<index>))W MSSQL, aby wymusić wybór indeksu dla konkretnego sprzężenia).
David Spillett,
Pytanie brzmi, czy date_dayrzeczywiście istnieje jakiś indeks . Jeśli nie ma, optymalizator nie ma wielu planów do porównania.
jkavalik