Dzisiaj ogarniemy, czym są węzły planu wykonania PostgreSQL, jak je czytać i – co najważniejsze – jak rozpoznać, że coś poszło nie tak. Dowiesz się, czemu twoja baza czasem wybiera kosztowny Seq Scan, mimo że przecież masz już indeks, a może nawet kilka.
Kiedy PostgreSQL buduje plan wykonania zapytania, dzieli go na etapy zwane węzłami. Każdy węzeł to taki "krok", który serwer bazy danych wykonuje, żeby obsłużyć twoje zapytanie. Podstawowe typy węzłów to:
Sequential Scan (Seq Scan)
Seq Scan czyli sekwencyjne skanowanie – to najprostszy sposób pobierania danych z tabeli. PostgreSQL dosłownie bierze tabelę, czyta jej wiersze jeden po drugim i sprawdza, czy pasują do warunków twojego zapytania.
Kiedy używany jest Seq Scan?
Seq Scan pojawia się, jeśli:
- W tabeli nie ma odpowiedniego indeksu, który mógłby przyspieszyć zapytanie.
- Warunek filtrowania jest zbyt ogólny, żeby indeks był przydatny (np. pobierasz ponad 50% danych).
- PostgreSQL uznaje, że sekwencyjne czytanie tabeli będzie szybsze niż użycie indeksu (czasem tak jest przy bardzo małych tabelach).
EXPLAIN SELECT * FROM students WHERE age > 18;
Przykład wyniku:
Seq Scan on students (cost=0.00..35.50 rows=10 width=50)
Filter: (age > 18)
Zwróć uwagę na Seq Scan on students — to PostgreSQL mówi, że będzie czytał całą tabelę "students".
Problemy z Seq Scan: Jeśli tabela jest ogromna, sekwencyjne skanowanie może zająć naprawdę dużo czasu.
Index Scan
Index Scan – to skanowanie danych z użyciem indeksu. Kiedy tworzysz indeks w PostgreSQL, to trochę jakbyś zrobił spis treści dla swojej tabeli. Jeśli zapytanie może użyć indeksu, PostgreSQL nie przegląda całej tabeli, tylko te fragmenty, które są potrzebne.
Kiedy używany jest Index Scan?
- W zapytaniu są warunki filtrowania na kolumnie z indeksem (np.
WHERE). - Używasz operatorów porównania, takich jak
=,<,>,BETWEENitd.
CREATE INDEX idx_students_age ON students(age);
EXPLAIN SELECT * FROM students WHERE age = 18;
Przykład wyniku:
Index Scan using idx_students_age on students (cost=0.15..8.27 rows=1 width=50)
Index Cond: (age = 18)
Tutaj Index Scan using idx_students_age pokazuje, że PostgreSQL używa indeksu idx_students_age. Zamiast czytać tabelę wiersz po wierszu, dostęp do danych jest dużo szybszy dzięki indeksowi.
Zalety Index Scan:
- Duże przyspieszenie zapytań na dużych tabelach.
- Mniej danych do czytania z dysku.
Problemy Index Scan:
Jeśli twoje zapytanie zwraca za dużo danych (np. ponad połowę tabeli), użycie indeksu może być nawet wolniejsze niż Seq Scan.
Hash Join
Hash Join jest używany do łączenia dwóch tabel na podstawie warunku join (np. ON students.course_id = courses.id). PostgreSQL tworzy hash table dla jednej z tabel (tej mniejszej) i używa jej do szukania dopasowań w drugiej tabeli.
Kiedy używany jest Hash Join?
- Przy łączeniu tabel przez
INNER JOIN,LEFT JOINitd. - Kiedy PostgreSQL uznaje
Hash Joinza bardziej wydajny niż inne metody join.
EXPLAIN
SELECT *
FROM students
JOIN courses ON students.course_id = courses.id;
Przykład wyniku:
Hash Join (cost=25.00..50.00 rows=10 width=100)
Hash Cond: (students.course_id = courses.id)
-> Seq Scan on students (cost=0.00..20.00 rows=10 width=50)
-> Hash (cost=15.00..15.00 rows=10 width=50)
-> Seq Scan on courses (cost=0.00..15.00 rows=10 width=50)
Tutaj Hash Join łączy dwie tabele. Zwróć uwagę, że PostgreSQL najpierw robi Seq Scan dla obu tabel, a potem buduje hash table (Hash).
Zalety Hash Join:
- Szybkie działanie dla tabel średniej wielkości.
- Efektywny przy łączeniu tabel z dużą liczbą wierszy.
Problemy Hash Join:
Jeśli hash table jest większy niż dostępna pamięć, PostgreSQL użyje dysku do jej przechowywania, co mocno spowalnia join.
Przykład analizy planu wykonania
Przeanalizujmy prawdziwy przykład.
Zapytanie:
EXPLAIN ANALYZE
SELECT *
FROM students
JOIN courses ON students.course_id = courses.id
WHERE students.age > 18;
Wynik:
Hash Join (cost=35.00..75.00 rows=5 width=100) (actual time=1.00..2.50 rows=5 loops=1)
Hash Cond: (students.course_id = courses.id)
-> Seq Scan on students (cost=0.00..40.00 rows=10 width=50) (actual time=0.50..1.00 rows=7 loops=1)
Filter: (age > 18)
Rows Removed by Filter: 3
-> Hash (cost=25.00..25.00 rows=5 width=50) (actual time=0.30..0.30 rows=5 loops=1)
-> Seq Scan on courses (cost=0.00..20.00 rows=5 width=50) (actual time=0.20..0.25 rows=5 loops=1)
Planning Time: 0.50 ms
Execution Time: 3.00 ms
Interpretacja:
Hash Join: Główny węzeł. PostgreSQL łączy tabelestudentsicourses.actual time: od 1.00 do 2.50 ms.rows=5: zapytanie zwróciło 5 wierszy.
- Zagnieżdżone węzły:
Seq Scan on students: sekwencyjnie czyta tabelęstudentsi stosuje filtr(age > 18).Rows Removed by Filter = 3: 3 wiersze nie spełniły warunku.Hash: PostgreSQL tworzy hash table dla tabelicourses.
Porównanie i wybór węzłów
Kiedy analizujesz plan wykonania, kluczowe jest zrozumienie, czemu PostgreSQL wybrał taki, a nie inny sposób przetwarzania danych. Czasem musisz zareagować, żeby poprawić sytuację – np. dodać indeks albo przepisać zapytanie. Kilka tipów:
- Jeśli widzisz
Seq Scanna dużej tabeli, pomyśl o indeksach. - Jeśli
Hash Joinjest za wolny, sprawdź ilość dostępnej pamięci dla PostgreSQL. - Używaj
EXPLAIN ANALYZE, żeby porównać przewidywane i rzeczywiste wartości metryk (rows,time).
Na tym etapie masz już podstawową wiedzę, jak czytać plany wykonania zapytań i interpretować ich węzły. W kolejnych lekcjach pogadamy o typowych problemach z optymalizacją i jak sobie z nimi radzić.
GO TO FULL VERSION