Odpowiedzi:
Pomocne może też być wyjaśnienie_EXPLAIN.pdf .
Część, która zawsze wydawała mi się myląca, to koszt uruchomienia w porównaniu z całkowitym kosztem. Google to za każdym razem, gdy o tym zapominam, co prowadzi mnie z powrotem tutaj, co nie wyjaśnia różnicy, dlatego piszę tę odpowiedź. Oto, co zebrałem z dokumentacji PostgresaEXPLAIN
, wyjaśnione tak , jak rozumiem.
Oto przykład z aplikacji zarządzającej forum:
EXPLAIN SELECT * FROM post LIMIT 50;
Limit (cost=0.00..3.39 rows=50 width=422)
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
Oto graficzne wyjaśnienie z PgAdmin:
(Kiedy używasz PgAdmin, możesz wskazać myszą komponent, aby odczytać szczegóły kosztów).
Koszt jest przedstawiany jako krotka, np. Koszt LIMIT
jest, cost=0.00..3.39
a koszt sekwencyjnego skanowania post
to cost=0.00..15629.12
. Pierwsza liczba w krotce to koszt uruchomienia, a druga liczba to całkowity koszt . Ponieważ użyłem, EXPLAIN
a nie EXPLAIN ANALYZE
, koszty te są szacunkowe, a nie rzeczywiste.
Jako komplikacja, koszt każdego węzła „nadrzędnego” obejmuje koszt jego węzłów podrzędnych. W reprezentacji tekstowej drzewo jest reprezentowane przez wcięcie, np. LIMIT
Jest węzłem macierzystym i Seq Scan
jego dzieckiem. W reprezentacji PgAdmin strzałki wskazują kierunek od dziecka do rodzica - kierunek przepływu danych - co może być sprzeczne z intuicją, jeśli znasz teorię grafów.
Dokumentacja mówi, że koszty obejmują wszystkie węzły podrzędne, ale zauważ, że całkowity koszt rodzica 3.39
jest znacznie mniejszy niż całkowity koszt jego dziecka 15629.12
. Całkowity koszt nie jest wliczony w cenę, ponieważ komponent taki jak LIMIT
nie musi przetwarzać całego wkładu. Zobacz EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;
przykład w dokumentacji PostgresEXPLAIN
.
W powyższym przykładzie czas uruchamiania wynosi zero dla obu składników, ponieważ żaden składnik nie musi wykonywać żadnego przetwarzania przed rozpoczęciem zapisywania wierszy: skanowanie sekwencyjne odczytuje pierwszy wiersz tabeli i emituje go. LIMIT
Czyta swój pierwszy wiersz, a następnie wysyła go.
Kiedy komponent musiałby dużo przetwarzać, zanim zacząłby wyprowadzać jakiekolwiek wiersze? Istnieje wiele możliwych powodów, ale spójrzmy na jeden jasny przykład. Oto to samo zapytanie co wcześniej, ale teraz zawierające ORDER BY
klauzulę:
EXPLAIN SELECT * FROM post ORDER BY body LIMIT 50;
Limit (cost=23283.24..23283.37 rows=50 width=422)
-> Sort (cost=23283.24..23859.27 rows=230412 width=422)
Sort Key: body
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
I graficznie:
Ponownie, skanowanie sekwencyjne post
nie wiąże się z żadnymi kosztami początkowymi: natychmiast rozpoczyna wyświetlanie wierszy. Ale sortowanie wiąże się ze znacznymi kosztami początkowymi, 23283.24
ponieważ musi posortować całą tabelę, zanim będzie można wyświetlić nawet jeden wiersz . Całkowity koszt sortowania 23859.27
jest tylko nieznacznie wyższy niż koszt uruchomienia, co odzwierciedla fakt, że po posortowaniu całego zbioru danych posortowane dane mogą być emitowane bardzo szybko.
Zauważ, że czas uruchamiania LIMIT
23283.24
jest dokładnie równy czasowi uruchamiania tego rodzaju. Nie dzieje się tak dlatego LIMIT
, że sam ma długi czas uruchamiania. W rzeczywistości ma sam zerowy czas uruchamiania, ale EXPLAIN
sumuje wszystkie koszty podrzędne dla każdego rodzica, więc LIMIT
czas uruchamiania obejmuje sumę czasów uruchamiania jego elementów podrzędnych .
Ta kumulacja kosztów może utrudnić zrozumienie kosztów wykonania każdego pojedynczego komponentu. Na przykład nasz LIMIT
ma zerowy czas uruchamiania, ale na pierwszy rzut oka nie jest to oczywiste. Z tego powodu kilka innych osób połączyło się z wyjaśnieniem.depesz.com , narzędziem stworzonym przez Huberta Lubaczewskiego (aka depesz), które pomaga zrozumieć EXPLAIN
, między innymi, odejmując koszty dzieci od kosztów rodziców. Wspomina o innych zawiłościach w krótkim poście na blogu o swoim narzędziu.
Wykonuje się od najbardziej wciętego do najmniej wciętego i uważam, że od dołu do góry. (Jeśli więc istnieją dwie sekcje z wcięciem, pierwsza jest wykonywana w pierwszej kolejności w dół strony, a gdy spotykają się z drugą, wykonywana jest reguła łącząca je).
Chodzi o to, że na każdym etapie pojawia się 1 lub 2 zestawy danych, które docierają i są przetwarzane według jakiejś reguły. Jeśli tylko jeden zestaw danych, ta operacja jest wykonywana na tym zestawie danych. (Na przykład zeskanuj indeks, aby dowiedzieć się, jakie wiersze chcesz, przefiltruj zbiór danych lub posortuj go.) Jeśli dwa, to dwa zestawy danych są dwoma elementami, które są dalej wcięte i są połączone regułą, którą widzisz. Znaczenie większości reguł można dość łatwo odgadnąć (szczególnie jeśli wcześniej przeczytałeś kilka planów wyjaśniających), jednak możesz spróbować zweryfikować poszczególne pozycje, przeglądając dokumentację lub (łatwiej), po prostu wrzucając frazę do Google wraz z kilkoma słowami kluczowymi, takimi jak EXPLAIN
.
Nie jest to oczywiście pełne wyjaśnienie, ale zapewnia wystarczający kontekst, aby zwykle można było dowiedzieć się, co chcesz. Na przykład rozważ ten plan z rzeczywistej bazy danych:
explain analyze
select a.attributeid, a.attributevalue, b.productid
from orderitemattribute a, orderitem b
where a.orderid = b.orderid
and a.attributeid = 'display-album'
and b.productid = 'ModernBook';
------------------------------------------------------------------------------------------------------------------------------------------------------------
Merge Join (cost=125379.14..125775.12 rows=3311 width=29) (actual time=841.478..841.478 rows=0 loops=1)
Merge Cond: (a.orderid = b.orderid)
-> Sort (cost=109737.32..109881.89 rows=57828 width=23) (actual time=736.163..774.475 rows=16815 loops=1)
Sort Key: a.orderid
Sort Method: quicksort Memory: 1695kB
-> Bitmap Heap Scan on orderitemattribute a (cost=1286.88..105163.27 rows=57828 width=23) (actual time=41.536..612.731 rows=16815 loops=1)
Recheck Cond: ((attributeid)::text = 'display-album'::text)
-> Bitmap Index Scan on (cost=0.00..1272.43 rows=57828 width=0) (actual time=25.033..25.033 rows=16815 loops=1)
Index Cond: ((attributeid)::text = 'display-album'::text)
-> Sort (cost=15641.81..15678.73 rows=14769 width=14) (actual time=14.471..16.898 rows=1109 loops=1)
Sort Key: b.orderid
Sort Method: quicksort Memory: 76kB
-> Bitmap Heap Scan on orderitem b (cost=310.96..14619.03 rows=14769 width=14) (actual time=1.865..8.480 rows=1114 loops=1)
Recheck Cond: ((productid)::text = 'ModernBook'::text)
-> Bitmap Index Scan on id_orderitem_productid (cost=0.00..307.27 rows=14769 width=0) (actual time=1.431..1.431 rows=1114 loops=1)
Index Cond: ((productid)::text = 'ModernBook'::text)
Total runtime: 842.134 ms
(17 rows)
Spróbuj sam to przeczytać i sprawdź, czy ma to sens.
Czytałem, że baza danych najpierw skanuje id_orderitem_productid
indeks, używając go do znajdowania wierszy, z których chce orderitem
, a następnie sortuje ten zestaw danych za pomocą szybkiego sortowania (użyte sortowanie zmieni się, jeśli dane nie mieszczą się w pamięci RAM), a następnie odkłada to na bok.
Następnie skanuje, orditematt_attributeid_idx
aby znaleźć żądane wiersze, orderitemattribute
a następnie sortuje ten zestaw danych za pomocą szybkiego sortowania.
Następnie pobiera oba zestawy danych i łączy je. (Łączenie przez scalanie to rodzaj operacji „kompresowania”, w której dwa posortowane zbiory danych są równoległe i emituje połączone wiersze, gdy są zgodne).
Jak powiedziałem, przechodzisz przez wewnętrzną część planu do części zewnętrznej, od dołu do góry.
Dostępne jest również narzędzie pomocnicze online, Depesz , które wskaże drogie części wyników analizy.
ma również jeden, oto te same wyniki , które według mnie wyjaśniają, gdzie jest problem.
PgAdmin pokaże graficzną reprezentację planu wyjaśniania. Przełączanie się między nimi i z powrotem może naprawdę pomóc ci zrozumieć, co oznacza reprezentacja tekstu. Jeśli jednak chcesz tylko wiedzieć, co się dzieje, możesz po prostu zawsze używać GUI.
Oficjalna dokumentacja PostgreSQL dostarcza interesującego, dokładnego wyjaśnienia, jak rozumieć dane wyjściowe wyjaśnienia.