czwartek, 24 kwietnia 2014

Obliczanie selektywności szukanych wartości na podstawie statystyk PostgreSQLa

W PostgreSQLu możemy zobaczyć informację na temat statystyk danych w naszych tabelkach. Ile mamy unikalnych wartości? Ile jest wartości NULLowych? Ale właściwie to po co nam to wiedzieć? Optymalizator w momencie gdy dostaje do wykonania zapytanie, musi podjąć decyzję jak to zapytanie wykonać. Do tego celu potrzebuje wiedzieć ile lub jaki jest rząd wielkości wierszy, z poszukiwanymi wartościami.
Pokaże Wam jak samemu możemy się dowiedzieć na podstawie statystyk, ile wierszy powinno zwrócić (wziąć pod uwagę) nasze zapytanie.

Do podejrzenia statystyk wykorzystujemy widok pg_stats. Informacje te pochodzą z tabeli pg_statistics (katalog systemowy, dostępny tylko dla super-użytkownika). Więcej informacji na temat pg_statistics znajdziecie tutaj.

Informację, które możecie odczytać z pg_stats:
  • schemaname - nazwa schematu, której znajduje się tabela
  • tablename - nazwa tabeli
  • attname - nazwa kolumny, dla której przestawiane są statystyki
  • inherited - przyjmuje wartości boolowskie. Jeśli jest true w wierszach są kolumny dziedziczące
  • null_frac - współczynnik ilości wierszy, które zawierają wartości NULL
  • avg_width - średnia długość danych w wierszach podanych w bajtach
  • n_distinct - jeśli wartość jest dodatnia, jest estymacją ilości wierszy o wartościach unikalnych. Jeśli jest wartością ujemną, jest liczbą unikalnych wartości podzielną przez ilość wierszy
  • most_common_vals - lista najczęściej występujących wartości w kolumnie
  • most_common_freqs - lista współczynników wystąpień wartości w tabeli most_common_vals
  • histogram_bounds - lista wartości, które dzielą wszystkie wartości w kolumnie na równe grupy. Dane nie są podawane jeśli typ kolumny nie uwzględnia operatora '<' przy porównywaniu danych lub jeśli most_common_vals występują wszystkie możliwe wartości w kolumnie 
  • correlations - statystyczna korelacja uporządkowania wartości kolumn. Podobnie jak dla histogram_bounds tylko dla kolumn które uwzględniają operator '<', dana jest podawana

Od wersji 9.2 doszły dodatkowe informacje: most_common_elems (najczęściej występujące wartości nienullowe), most_common_elem_freqs (współczynnik wystąpień najczęściej występujących wartości nienullowych), elem_count_histogram (histogramu liczby rodzajów różnych nie-zerowych wartości elementów w obrębie wartości kolumny, a następnie przez średnią liczbę odrębnych elementów niezerowych).

Statystyki w praktyce

Przykład tabeli na której będziemy pracować:

               Table "public.event2"
+--------+-----------------------------+-----------+
| Column |            Type             | Modifiers |
+--------+-----------------------------+-----------+
| c1     | integer                     | not null  |
| c2     | character varying(20)       |           |
| c3     | timestamp without time zone |           |
| c4     | integer                     |           |
| c5     | integer                     |           |
+--------+-----------------------------+-----------+

Nasza tabelka ma 500 wierszy. Jeśli jednak nie znany ilości wierszy, mamy dwa wyjścia: policzyć je funkcją count lub sprawdzić na tabeli pg_class:

127.0.0.1:7432 postgres@test # SELECT reltuples, relpages FROM pg_class WHERE relname = 'event2';
+-----------+----------+
| reltuples | relpages |
+-----------+----------+
|       500 |        9 |
+-----------+----------+
(1 row)

Time: 0,442 ms

Statystyki dla kolumny c5:

127.0.0.1:7432 postgres@test # select * from pg_stats where tablename = 'event2' and attname = 'c5';
+-[ RECORD 1 ]------+---------------------------------------------------------------------------------------------------------------------+
| schemaname        | public                                                                                                              |
| tablename         | event2                                                                                                              |
| attname           | c5                                                                                                                  |
| inherited         | f                                                                                                                   |
| null_frac         | 0                                                                                                                   |
| avg_width         | 4                                                                                                                   |
| n_distinct        | 20                                                                                                                  |
| most_common_vals  | {19,16,4,5,6,9,0,12,18,17,3,1,2,15,7,11,10,13,8,14}                                                                 |
| most_common_freqs | {0.074,0.062,0.06,0.06,0.06,0.058,0.056,0.054,0.054,0.052,0.05,0.048,0.048,0.042,0.04,0.04,0.038,0.036,0.034,0.034} |
| histogram_bounds  | NULL                                                                                                                |
| correlation       | 0.067399                                                                                                            |
+-------------------+---------------------------------------------------------------------------------------------------------------------+

Time: 7,469 ms

Chcemy określić ile wierszy pasuje do warunku c2 = 19. Bazując na danych ze statystyk wiemy, że w kolumnie mamy:
  • 20 unikalnych wartości
  • nie ma wartości NULL
  • przykładowo wartość 19 występuje w 7.4% wszystkich wartości czyli dla 500 wszystkich wierszach, jest ich 37 ( => 500 * 7.4%)
Porównajmy nasze obliczenia z explainem (patrzcie na informacje rows):

127.0.0.1:7432 postgres@test # explain select * from event2 where c5 = 19;
+---------------------------------------------------------+
|                       QUERY PLAN                        |
+---------------------------------------------------------+
| Seq Scan on event2  (cost=0.00..15.25 rows=37 width=40) |
|   Filter: (c5 = 19)                                     |
+---------------------------------------------------------+
(2 rows)

Time: 0,356 ms

Podobnie mamy selektywność dla kolumny c2, o ile szukana wartość znajduje się w najczęsciej występujących wartościach:

127.0.0.1:7432 postgres@test # select * from pg_stats where tablename = 'event2' and attname = 'c2';
+-[ RECORD 1 ]------+---------------------------------+
| schemaname        | public                          |
| tablename         | event2                          |
| attname           | c2                              |
| inherited         | f                               |
| null_frac         | 0                               |
| avg_width         | 6                               |
| n_distinct        | 5                               |
| most_common_vals  | {0074a,97a14,f87fa,dc358,85c58} |
| most_common_freqs | {0.336,0.336,0.208,0.066,0.054} |
| histogram_bounds  | NULL                            |
| correlation       | 0.0551021                       |
+-------------------+---------------------------------+

Time: 1,243 ms

Jeśli jednak naszej wartości tam nie ma, selektywność obliczamy następująco:
(1 - sum(most_common_freqs))/(num_distinct / most_common_vals_nr)

A dla naszej tabeli/kolumny będzie to wynosiło:
(1 - (0.336+0.336+0.208+0.066+0.054)) / (5 / 5) = 0


Inny przykład gdy mamy tylko wartości unikalne. Statystyki dla kolumny c1:

127.0.0.1:7432 postgres@test # select * from pg_stats where tablename = 'event2' and attname = 'c1';
+-[ RECORD 1 ]------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| schemaname        | public                                                                                                                                                                                                                                                                                                                                                                                          |
| tablename         | event2                                                                                                                                                                                                                                                                                                                                                                                          |
| attname           | c1                                                                                                                                                                                                                                                                                                                                                                                              |
| inherited         | f                                                                                                                                                                                                                                                                                                                                                                                               |
| null_frac         | 0                                                                                                                                                                                                                                                                                                                                                                                               |
| avg_width         | 4                                                                                                                                                                                                                                                                                                                                                                                               |
| n_distinct        | -1                                                                                                                                                                                                                                                                                                                                                                                              |
| most_common_vals  | NULL                                                                                                                                                                                                                                                                                                                                                                                            |
| most_common_freqs | NULL                                                                                                                                                                                                                                                                                                                                                                                            |
| histogram_bounds  | {1,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100,105,110,115,120,125,130,135,140,145,150,155,160,165,170,175,180,185,190,195,200,205,210,215,220,225,230,235,240,245,250,255,260,265,270,275,280,285,290,295,300,305,310,315,320,325,330,335,340,345,350,355,360,365,370,375,380,385,390,395,400,405,410,415,420,425,430,435,440,445,450,455,460,465,470,475,480,485,490,495,500} |
| correlation       | 0.998155                                                                                                                                                                                                                                                                                                                                                                                        |
+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

Time: 1,250 ms

Tym razem wzór selektywności na podstawie histogram_bounds wygląda następująco:

(full_his_bound_count + (value - his_bound[nr].min)/(his_bound[nr].max - his_bound[nr].min)) / num_his_bound

Gdzie:
  • full_his_bround_count - ilość przedziałów, które w całości bierzemy pod uwagę przy obliczaniu selektywności.
  • value - nasza wartość dla której obliczamy selektywność
  • his_bound[nr] - przedział, w którym jest nasza wartość. Np dla wartości = 8 jest to przedział 2, pomiędzy 5 (min), a 10 (max).
  • num_his_bound - ilość przedziałów w kolumnie histogram_bounds. W powyższym przykładzie wartości mamy od 1 do 500 co 5 dlatego 500/5 = 100

Obliczanie selektywności rekordów dla warunków:
  • c1 < 8, selektywność = (1 + (8 - 5)/(10 - 5))/(100) = 8
  • c1 < 28, selektywność = (5 + (28 - 25)/(30 - 25))/(100) = 28

Sprawdźmy nasze wyniki:

127.0.0.1:7432 postgres@test # explain select * from event2 where c1 < 28;
+----------------------------------------------------------------------------+
|                                 QUERY PLAN                                 |
+----------------------------------------------------------------------------+
| Index Scan using event2_pkey on event2  (cost=0.00..8.86 rows=28 width=40) |
|   Index Cond: (c1 < 28)                                                    |
+----------------------------------------------------------------------------+
(2 rows)

Time: 0,311 ms

Obliczanie selektywności dla dwóch kolumn jest bardzo podobne. Na początku wychodzimy od obliczania selektywności każdej kolumny z osobna, mnożymy je i w ten sposób otrzymujemy selektywność całego warunku.

Przykład dla zapytania:
select * from event2 where c2 = '0074a' and c4 = 30
selektywność = ((selektywność dla c2) * (selektywność dla c4) * (ilość wierszy)) = 0.336 * 0.02 = 0.006
ilość wierszy = 0.006 * 500 = 3

127.0.0.1:7432 postgres@test # explain select * from event2 where c2 = '0074a' and c4 = 30;
+--------------------------------------------------------+
|                       QUERY PLAN                       |
+--------------------------------------------------------+
| Seq Scan on event2  (cost=0.00..16.50 rows=3 width=26) |
|   Filter: (((c2)::text = '0074a'::text) AND (c4 = 30)) |
+--------------------------------------------------------+
(2 rows)

Time: 0,456 ms

Odświeżanie informacji na temat statystyk

Dane w tabeli pg_statistics są aktualizowane na bieżąco oraz po wykonaniu poleceń: ANALYZE i VACCUM ANALYZE.

Aby zmniejszyć/zwiększyć ilość danych statystycznych możemy zmienić wartość zmiennej globalnej konfiguracyjnej: default_statistics_target (domyślnie
przyjmuje wartość 100).

O innych przykładach obliczania selektywności dla zapytań odsyłam do dokumentacji tutaj.

Brak komentarzy:

Prześlij komentarz