• Bazy danych i SQL
  • EXPLAIN ANALYZE - Czytanie planów zapytań SQL bez zgadywania

EXPLAIN ANALYZE - Czytanie planów zapytań SQL bez zgadywania

Jeremi Andrzejewski 19 kwietnia 2026
Cyfrowe obwody z niebieskimi światłami. Można analizować przepływ danych.

Spis treści

Polecenie EXPLAIN ANALYZE pokazuje dwa ważne elementy naraz: plan wykonania zapytania i rzeczywiste metryki jego działania. To właśnie dzięki temu można szybko sprawdzić, czy problem leży w indeksach, statystykach, zbyt kosztownym joinie, czy po prostu w złym założeniu co do rozmiaru danych.

W tym tekście wyjaśniam, jak czytać wynik bez zgadywania, na które liczby patrzeć najpierw i jak używać tego narzędzia bez ryzyka dla danych. Dorzucam też praktyczne różnice między PostgreSQL i MySQL, bo w praktyce to właśnie tam najczęściej pojawiają się nieporozumienia.

Najważniejsze informacje, które od razu porządkują temat

  • To polecenie uruchamia zapytanie i dopisuje do planu rzeczywiste czasy, liczbę wierszy oraz liczbę wykonań poszczególnych kroków.
  • Cost nie oznacza milisekund, tylko wewnętrzny koszt estymowany przez optymalizator.
  • Najpierw porównuję rows z actual rows, potem patrzę na loops i dopiero na ogólny czas.
  • W PostgreSQL `ANALYZE` w tym kontekście nie jest tym samym co osobne polecenie aktualizujące statystyki tabel.
  • Przy zapytaniach modyfikujących dane warto użyć transakcji z `ROLLBACK`, jeśli chcesz tylko profilować plan.
  • Wynik w MySQL i PostgreSQL jest podobny ideowo, ale format i szczegóły metryk są inne.

Co robi to polecenie i kiedy naprawdę pomaga

Ja traktuję to narzędzie jako diagnostykę wydajności, a nie zwykły opis zapytania. Zwykły `EXPLAIN` pokazuje, co planner zamierza zrobić, a wariant z analizą pokazuje jeszcze, co faktycznie się wydarzyło podczas wykonania.

To robi różnicę zwłaszcza wtedy, gdy zapytanie wygląda rozsądnie na papierze, ale w aplikacji działa wyraźnie wolniej, niż powinno. Wtedy widać czarno na białym, czy problemem jest skan całej tabeli, źle dobrany join, zbyt optymistyczna estymacja liczby wierszy, czy może kosztowny sort albo odczyt z dysku.

Warto też pamiętać o jednej pułapce: osobne polecenie `ANALYZE` służy do zbierania statystyk o tabelach, a nie do mierzenia czasu zapytania. To podobne słowo, ale zupełnie inna rola. Jeśli statystyki są stare, planner może się mylić, a wtedy nawet dobrze napisane SQL potrafi dostać zły plan. Gdy rozumiesz tę różnicę, łatwiej zdecydować, czy naprawiasz zapytanie, czy najpierw odświeżasz statystyki tabel.

W praktyce używam tego przy wolnych zapytaniach z aplikacji, przy audycie indeksów i przy sprawdzaniu, czy zmiana w schemacie faktycznie coś poprawiła. Następny krok to nauczyć się czytać wynik tak, żeby nie mylić estymacji z rzeczywistym wykonaniem.

Schemat optymalizacji zapytania SQL: analiza, wybór planu, przetwarzanie i generowanie wyników. Analizujemy zapytanie, by wybrać najlepszy plan wykonania, przetworzyć dane i zwrócić wyniki, optymalizując wydajność.

Jak czytać wynik bez zgadywania

Najważniejsza zasada jest prosta: nie zaczynam od czasu całkowitego. Najpierw patrzę na różnicę między tym, co planner przewidział, a tym, co naprawdę zaszło. Dopiero potem sprawdzam, gdzie ten rozdźwięk rośnie najbardziej.

Element wyniku Co oznacza Na co zwracam uwagę
cost Szacunkowy koszt operacji w wewnętrznych jednostkach optymalizatora Nie traktuję tego jak czasu. To tylko porównanie planów między sobą.
actual time Rzeczywisty czas wykonania danego kroku To są zwykle milisekundy. Tu widać, gdzie naprawdę ucieka czas.
rows Liczba wierszy przewidziana przez planner Porównuję to z wynikiem rzeczywistym, bo duża różnica zwykle oznacza problem ze statystykami albo selektywnością warunku.
loops Ile razy dany krok został wykonany Wysokie loops przy kosztownym kroku to częsty sygnał, że join działa nieefektywnie.
buffers Odczyty i zapisy buforów podczas planowania i wykonywania Pomaga rozstrzygnąć, czy problem wynika z I/O, a nie tylko z CPU.

W PostgreSQL warto też pamiętać, że actual time jest czasem rzeczywistym, a loops pokazuje liczbę wykonań kroku. Jeśli jakiś węzeł uruchamia się wiele razy, jego czas w planie jest zwykle pokazany jako średnia na jedno wykonanie. To bardzo pomaga przy nested loopach, gdzie pojedynczy krok wygląda niewinnie, ale po pomnożeniu przez liczbę powtórzeń robi się kosztowny.

Ja zwykle porównuję trzy rzeczy naraz: estymację wierszy, rzeczywiste wiersze i liczbę iteracji. Gdy te liczby są blisko siebie, plan zwykle jest zdrowy. Gdy różnice są duże, szukam przyczyny w danych, statystykach albo indeksach. To prowadzi wprost do sygnałów, które najczęściej zdradzają źródło problemu.

Jakie sygnały najczęściej zdradzają problem

Nie każdy dziwny plan oznacza awarię. Czasem wolniej działa po prostu duży zestaw danych. Ale są powtarzalne wzorce, które w praktyce od razu przyciągają moją uwagę.

Sygnał Co zwykle oznacza Co sprawdzić najpierw
Seq Scan na dużej tabeli Brak użytecznego indeksu albo filtr ma niską selektywność Warunek `WHERE`, istniejące indeksy i aktualność statystyk
Nested Loop z bardzo dużą liczbą loops Jedna strona joinu jest wykonywana wielokrotnie Czy join key jest indeksowany i czy kolejność joinów ma sens
Duża różnica między rows i rzeczywistą liczbą wierszy Planner źle ocenił selektywność albo rozkład danych jest nierówny Statystyki tabel, histogramy, skew danych
Sort Method z użyciem dysku Sort nie mieści się w pamięci i spada na wolniejsze I/O `work_mem`, rozmiar wyniku i to, czy sort jest w ogóle potrzebny
Wysokie odczyty buffers Zapytanie jest ograniczane przez dostęp do dysku lub cache nie pomaga Indeksy, lokalność danych i sposób łączenia tabel

Jest jeszcze jedna rzecz, którą początkujący często czytają źle: sequential scan nie zawsze jest zły. Na małej tabeli pełny skan bywa po prostu najszybszy. Problem zaczyna się wtedy, gdy ten sam wzorzec pojawia się na dużej tabeli, a planner wciąż nie ma lepszego wyboru, bo brakuje indeksu albo statystyki są nieaktualne.

Po samym planie zwykle da się wskazać kierunek naprawy, ale zanim coś zmienisz, trzeba zadbać o bezpieczny sposób testu. Właśnie tu najłatwiej popełnić kosztowny błąd.

Jak uruchamiać to bez ryzyka dla danych

W PostgreSQL i MySQL polecenie profilujące plan nadal uruchamia zapytanie. Dla `SELECT` to zwykle nie jest problem, ale dla `UPDATE`, `DELETE`, `INSERT` albo `MERGE` trzeba uważać, bo zmiany mogą się wykonać naprawdę. Jeśli chcę tylko sprawdzić plan dla zapytania modyfikującego dane, używam transakcji i kończę ją `ROLLBACK`.

BEGIN;
EXPLAIN (ANALYZE, BUFFERS)
UPDATE orders
SET status = 'archived'
WHERE created_at < now() - interval '180 days';
ROLLBACK;

To podejście daje mi pełny obraz wykonania, ale bez trwałego zapisu zmian. Dodatkowo warto pamiętać, że sam pomiar wprowadza niewielki narzut, więc wynik może być trochę wolniejszy niż zwykłe uruchomienie. Nie traktuję więc jednej liczby jako absolutnej prawdy. Patrzę raczej na różnice między wariantami tego samego zapytania.

Jeśli potrzebuję wyniku czytelnego dla narzędzia albo skryptu, wybieram format maszynowy. W PostgreSQL przydaje się JSON, XML albo YAML, a w MySQL często najbardziej użyteczny jest format drzewiasty lub JSON. To już nie jest kwestia smaku, tylko tego, jak później analizujesz wynik: ręcznie, w edytorze, czy automatycznie w pipeline.

Gdy bezpieczeństwo i interpretacja są już pod kontrolą, najciekawsze staje się porównanie silników. I tu różnice są większe, niż wielu osobom się wydaje.

PostgreSQL i MySQL pokazują podobne rzeczy, ale w innym układzie

Idea jest wspólna: zobaczyć plan i porównać go z rzeczywistym wykonaniem. Różni się jednak składnia, format wyjścia i to, na jakie szczegóły patrzysz jako pierwsze. To ważne, bo ktoś przyzwyczajony do jednego silnika potrafi źle odczytać wynik drugiego.

Aspekt PostgreSQL MySQL
Typowa składnia `EXPLAIN (ANALYZE, BUFFERS) ...` `EXPLAIN ANALYZE ...`
Domyślny układ wyniku Najczęściej tekstowy, ale dostępne są też formaty maszynowe Najczęściej układ drzewiasty, z możliwością JSON w wybranych przypadkach
Co pokazuje czas Czas rzeczywisty kroków i osobno czas planowania Czas rzeczywisty iteratorów, zwykle w milisekundach
Jak czytać koszt Cost to jednostki estymowane, nie czas Cost też jest estymacją, ale wynik jest prezentowany w innym stylu
Co jest szczególnie przydatne Buffers, różnice między estymacją a realnym wykonaniem, czas planowania Iterator tree, liczba pętli, czas pierwszego i ostatniego wiersza

W praktyce nie chodzi o to, który silnik „lepiej” pokazuje plan, tylko który lepiej pasuje do twojego sposobu diagnozowania. Ja przy PostgreSQL najpierw sprawdzam różnice między rows i actual rows oraz odczyty z buforów, a w MySQL mocniej patrzę na strukturę iteratorów i to, gdzie konkretnie rośnie liczba pętli. W obu przypadkach cel jest ten sam: zrozumieć, dlaczego optymalizator wybrał właśnie taki przebieg zapytania.

Jeśli plan nadal nie daje odpowiedzi, trzeba zejść o poziom niżej i sprawdzić, czy problem nie leży poza samym zapytaniem. To najczęściej oszczędza najwięcej czasu.

Co robię, gdy plan nadal nie wyjaśnia problemu

Jeżeli metryki wyglądają sensownie, a zapytanie wciąż działa wolno, wracam do rzeczy bardziej „ziemskich”: statystyk, indeksów, pamięci i realnego rozmiaru danych. W praktyce najczęściej winne są cztery rzeczy.

  • Nieaktualne statystyki tabel, przez które planner źle szacuje liczbę wierszy.
  • Zły indeks albo indeks, który nie pasuje do kolejności warunków w zapytaniu.
  • Zbyt mało pamięci roboczej, przez co sort lub hash ląduje na dysku.
  • Różnica między środowiskiem testowym a produkcyjnym, zwłaszcza przy dużej nierówności danych.

Tu bardzo pomaga rozdzielenie dwóch pojęć: analiza planu to nie to samo co analiza danych tabeli. Jeśli statystyki są stare, uruchamiam osobne `ANALYZE` na tabelach, które się mocno zmieniły. Jeśli problemem jest aplikacja w Pythonie, sprawdzam też, ile czasu zajmuje pobranie rekordów po stronie klienta, bo samo `EXPLAIN ANALYZE` nie liczy pełnego kosztu sieci i renderowania wyniku dla użytkownika.

W takich sytuacjach lubię iść małymi krokami: najpierw jeden indeks, potem jedna zmiana warunku, potem ponowny pomiar na podobnym zestawie danych. To daje lepszą odpowiedź niż jednoczesne poprawianie pięciu rzeczy, po którym nie wiadomo, co faktycznie zadziałało.

Najkrótsza droga od planu do szybszego SQL

Jeśli miałbym sprowadzić całą metodę do jednego schematu, wyglądałoby to tak: najpierw sprawdź, czy planner trafnie ocenił rozmiar danych, potem zobacz, czy zapytanie nie powtarza zbyt wielu kosztownych kroków, a na końcu popraw tylko ten element, który naprawdę robi różnicę. W większości przypadków wystarczy to, żeby z wolnego zapytania zrobić przewidywalne i stabilne.

Największą wartość daje mi nie sam wynik, tylko sposób myślenia, który za nim stoi. Gdy czytam plan konsekwentnie, przestaję zgadywać i zaczynam diagnozować. A to w SQL zwykle oznacza krótszą drogę do konkretnej poprawy: indeksu, statystyki, zmiany joinu albo prostszego warunku, który lepiej pasuje do danych.

FAQ - Najczęstsze pytania

EXPLAIN pokazuje plan, który optymalizator zamierza wykonać. EXPLAIN ANALYZE faktycznie uruchamia zapytanie i dodaje do planu rzeczywiste metryki, takie jak czas wykonania, liczba wierszy i liczba pętli dla każdego kroku, co pozwala porównać estymacje z rzeczywistością.

"Cost" to estymowany koszt operacji w wewnętrznych jednostkach optymalizatora, a nie rzeczywisty czas w milisekundach. Służy do porównywania różnych planów między sobą. Zapytanie z wysokim kosztem może być szybkie, jeśli rzeczywiste metryki (actual time, actual rows) są niskie.

Dla zapytań UPDATE, DELETE, INSERT użyj transakcji z ROLLBACK. Uruchom EXPLAIN ANALYZE wewnątrz bloku BEGIN; ... ROLLBACK;, aby zapytanie się wykonało i dostarczyło metryki, ale zmiany nie zostały trwale zapisane w bazie danych.

Najpierw porównaj przewidzianą liczbę wierszy (rows) z rzeczywistą (actual rows). Duże różnice wskazują na problemy ze statystykami lub selektywnością warunków. Następnie zwróć uwagę na liczbę pętli (loops) i dopiero potem na rzeczywisty czas wykonania (actual time) poszczególnych kroków.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

explain analyze
explain analyze postgresql
explain analyze mysql
Autor Jeremi Andrzejewski
Jeremi Andrzejewski
Jestem Jeremi Andrzejewski, doświadczonym twórcą treści i analitykiem branżowym, specjalizującym się w technologiach. Od ponad pięciu lat zajmuję się analizowaniem trendów w branży technologicznej oraz pisaniem artykułów, które mają na celu przybliżenie złożonych zagadnień w przystępny sposób. Moje zainteresowania obejmują nowe technologie, innowacje oraz ich wpływ na codzienne życie i biznes. W swojej pracy kładę duży nacisk na rzetelność i aktualność informacji. Staram się dostarczać czytelnikom obiektywne analizy oraz sprawdzone dane, które mogą pomóc im w podejmowaniu świadomych decyzji. Moim celem jest nie tylko informowanie, ale także inspirowanie do odkrywania możliwości, jakie niesie ze sobą rozwój technologii. Wierzę, że wiedza powinna być dostępna dla każdego, dlatego dokładam wszelkich starań, aby moje teksty były zrozumiałe i użyteczne.

Udostępnij artykuł

Napisz komentarz