wtorek, 21 stycznia 2014

EXPLAIN plan w PostgreSQL v9.1 - Zrozumieć co dzieje się z naszym zapytaniem

W poprzednim poście (EXPLAIN plan w PostgreSQL v9.1 - Wprowadzenie), dowiedzieliśmy się jakie opcje może przyjmować polecenie EXPLAIN i jak może wyglądać rezultat działania tej instrukcji. Teraz jednak chcieli byśmy wiedzieć co oznacza ten wynik. Czy nasze zapytanie zostało dobrze napisane? Czy działa efektywnie?

Przykładowy wynik działa instrukcji EXPLAIN:

postgres@test(127.0.0.1) # explain select * from event e left join event_2 ee on e.c4 = ee.c1 where (e.c4 < 16 AND e.c5 = 90) and ee.c2 > 10;
                                       QUERY PLAN                                        
-----------------------------------------------------------------------------------------
 Hash Join  (cost=6809.87..12892.11 rows=1661 width=56)
   Hash Cond: (e.c4 = ee.c1)
   ->  Bitmap Heap Scan on event e  (cost=6806.56..12864.79 rows=1974 width=40)
         Recheck Cond: ((c5 = 90) AND (c4 < 16))
         ->  BitmapAnd  (cost=6806.56..6806.56 rows=1974 width=0)
               ->  Bitmap Index Scan on idx_c5  (cost=0.00..177.89 rows=12473 width=0)
                     Index Cond: (c5 = 90)
               ->  Bitmap Index Scan on idx_c4  (cost=0.00..6627.44 rows=211049 width=0)
                     Index Cond: (c4 < 16)
   ->  Hash  (cost=2.25..2.25 rows=85 width=16)
         ->  Seq Scan on event_2 ee  (cost=0.00..2.25 rows=85 width=16)
               Filter: (c2 > 10)
(12 rows)

lub graficzny z pgAdmin III:


Jeśli będziemy wiedzieć jak odczytywać wynik działania polecenia EXPLAIN, będziemy też wiedzieć:
  • jaka metoda skanowania została użyta (Scan Methods)
    • Sequential Scan
    • Index Scan
    • Bitmap Scan
  • jaka metoda łączenia została użyta (Join Methods)
    • Nested Loop
      • With Inner Sequential Scan
      • With Inner Index Scan
    • Hash Join
    • Merge Join
  • jaki kierunek łączenia został zastosowany (Join Order)
  • jaki index został użyty
  • jaki filtry danych zostały zastosowane
  • oraz informacje, które zostały omówione we wprowadzeniu (koszty, użycie bufora, czas trwania i inne (które można zaleźć pod postem: EXPLAIN plan w PostgreSQL v9.1 - Wprowadzenie))


Metody skanowania


Sequential Scan

Najgorszy sposób skanowania danych. Optymalizator przechodzi przez tabelę, aby wybrać wiersze, które go interesują.

Ten sposób jest wybierany głównie gdy:
  • na kolumnie na której został nałożony warunek, nie mamy index-u
  • optymalizator zdecydował, że nasz warunek dotyczy większości danych w tabeli
Przykład:
postgres@test(127.0.0.1) # explain (ANALYZE, buffers true) select c5 from event offset 10 limit 10;
                                                   QUERY PLAN                                                   
----------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.29..0.58 rows=10 width=4) (actual time=0.044..0.066 rows=10 loops=1)
   Buffers: shared read=1
   ->  Seq Scan on event  (cost=0.00..38333.33 rows=1333333 width=4) (actual time=0.020..0.040 rows=20 loops=1)
         Buffers: shared read=1
 Total runtime: 0.097 ms
(5 rows)


Index Scan



Skanuje index, a następnie wybiera dane z tabeli aby je zwrócić.

Koło informacji, że index jest skanowany widzimy, który index został użyty i na jakiej tabeli:

Index Scan using idx_c5 on event

Przykład:


postgres@test(127.0.0.1) # explain select c5 from event order by c5 offset 10 limit 10;
                                      QUERY PLAN                                      
--------------------------------------------------------------------------------------
 Limit  (cost=16.31..32.62 rows=10 width=4)
   ->  Index Scan using idx_c5 on event  (cost=0.00..2174766.57 rows=1333333 width=4)
(2 rows)


Bitmap Scan



Tym razem chcemy wybrać dane, gdzie warunek jest spełniony przez dwa index-y.

Optymalizator wykonuje następujące kroki:
  1. Buduje bitmapę wierszy, które to spełniają pierwszy warunek, używa do tego index-u, który spełnia ten warunek (-> Bitmap Index Scan on idx_c5)
  2. Buduje bitmapę wierszy, które to spełniają drugi warunek, także, używa do tego index-u, który spełnia ten warunek (-> Bitmap Index Scan on idx_c4)
  3. Łączy powstałe w 1 i 2 krokach bitmap-y przy pomocy operatora użytego w warunkach zapytania (BitmapOr dla operatora OR, BitmapAnd dla operatora AND) -> BitmapOr
  4. Przegląda tabelę docelową z danymi (-> Bitmap Heap Scan on event) i ponownie sprawdza czy spełniony został nasz warunek (-> Recheck Cond: ((c4 = 16) OR (c5 = 30)))
Przykład:
postgres@test(127.0.0.1) # explain select * from event where (c4 = 16 OR c5 = 30);
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Bitmap Heap Scan on event  (cost=631.46..27250.57 rows=28181 width=40)
   Recheck Cond: ((c4 = 16) OR (c5 = 30))
   ->  BitmapOr  (cost=631.46..631.46 rows=28328 width=0)
         ->  Bitmap Index Scan on idx_c4  (cost=0.00..389.03 rows=12328 width=0)
               Index Cond: (c4 = 16)
         ->  Bitmap Index Scan on idx_c5  (cost=0.00..228.34 rows=16000 width=0)
               Index Cond: (c5 = 30)
(7 rows)


Metody łączenia


Każdy sposób łączenia przyjmuje dwa argumenty, jakimi są: lewa i prawa relację.

Nested Loop With Inner Sequential Scan




Dla bardzo dużych tabel, ten sposób łączenia może być bardzo kosztowny gdyż w takim przypadku optymalizator: dla każdego elementu relacji lewej, sprawdza każdy wiersz relacji prawej. Jeśli warunek łączenia jest spełniony, zwraca wiersz jako wynik łączenia. W przeciwnym przypadku, wiersz jest pomijany.



Przykład:


postgres@test(127.0.0.1) # explain select * from (select * from event limit 10) as foo, event_2 limit 10;
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
 Limit  (cost=0.00..0.15 rows=10 width=56)
   ->  Nested Loop  (cost=0.00..14.91 rows=1000 width=56)
         ->  Seq Scan on event_2  (cost=0.00..2.00 rows=100 width=16)
         ->  Materialize  (cost=0.00..0.44 rows=10 width=40)
               ->  Limit  (cost=0.00..0.29 rows=10 width=40)
                     ->  Seq Scan on event  (cost=0.00..38333.33 rows=1333333 width=40)
(6 rows)


Nested Loop With Inner Index Scan



Tym razem jednak do wybierana danych z tabeli prawej, używany jest index.








Przykład:
postgres@test(127.0.0.1) # explain select * from event where c1 IN (select c1 from (select count(*) as c, min(c1) as c1, c4 from event where c3 > '2014-01-01' group by c4 having count(*) > 1500) as foo);
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Nested Loop  (cost=43606.41..43734.71 rows=15 width=40)
   ->  HashAggregate  (cost=43606.41..43606.56 rows=15 width=4)
         ->  HashAggregate  (cost=43606.03..43606.22 rows=15 width=8)
               Filter: (count(*) > 1500)
               ->  Seq Scan on event  (cost=0.00..41666.66 rows=193937 width=8)
                     Filter: (c3 > '2014-01-01 00:00:00'::timestamp without time zone)
   ->  Index Scan using event_pkey on event  (cost=0.00..8.53 rows=1 width=40)
         Index Cond: (c1 = (min(public.event.c1)))
(8 rows)


Hash Join



Prawa relacja jest skanowana i ładowana do tabeli hash-y w taki sposób, że atrybuty łączenia są kluczami hash-y. Następnie lewa relacja jest skanowana i odpowiednie wartości są znalezione poprzez klucze hash-y (relacje prawą).

Algorytm jest bardzo szybki ale wymaga wystarczająco dużo pamięci, aby pomieścić prawą relację.Nie gwarantuje także, że sortowanie wejściowe będzie zachowane w wyniku łączenia.

Przykład:


postgres@test(127.0.0.1) # explain select * from t3 left join t1 on t1.c1 = t3.c1;
                            QUERY PLAN                             
-------------------------------------------------------------------
 Hash Left Join  (cost=35.43..96.25 rows=2140 width=50)
   Hash Cond: (t3.c1 = t1.c1)
   ->  Seq Scan on t3  (cost=0.00..31.40 rows=2140 width=8)
   ->  Hash  (cost=21.30..21.30 rows=1130 width=42)
         ->  Seq Scan on t1  (cost=0.00..21.30 rows=1130 width=42)
(5 rows)


Merge Join



Zanim dojdzie do łączenia, relacje (prawa i lewa) są sortowane.
Następnie skanuje równolegle dopasowując równe wartości.

Zazwyczaj algorytm skanuje wiersz tylko raz, ale pod warunkiem, że w relacji lewej nie ma duplikatów.


Przykład:
postgres@test(127.0.0.1) # explain select * from (select * from event order by c2 desc) e1 join (select * from event order by c4 desc) e2 on e1.c5 = e2.c4;
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
 Merge Join  (cost=828400.83..134185000.82 rows=8888884444 width=80)
   Merge Cond: (public.event.c4 = public.event.c5)
   ->  Sort  (cost=283355.83..286689.17 rows=1333333 width=40)
         Sort Key: public.event.c4
         ->  Seq Scan on event  (cost=0.00..38333.33 rows=1333333 width=40)
   ->  Materialize  (cost=545045.00..551711.67 rows=1333333 width=40)
         ->  Sort  (cost=545045.00..548378.33 rows=1333333 width=40)
               Sort Key: public.event.c5
               ->  Sort  (cost=283355.83..286689.17 rows=1333333 width=40)
                     Sort Key: public.event.c2
                     ->  Seq Scan on event  (cost=0.00..38333.33 rows=1333333 width=40)
(11 rows)



To jaką decyzje podejmie optymalizator, który algorytm użyć, jak wybrać dane, jest uzależnione od statystyk. Dlatego ważne jest aby było one zawsze aktualne.
Ale o samych statystykach może kiedy indziej.

Miłego czytania!

Brak komentarzy:

Prześlij komentarz