Nieoczekiwane skanowanie sekwencji podczas wykonywania zapytania na wartość logiczną o wartości NULL

10

Mam kolumnę bazy danych o nazwie, auto_reviewgdzie jest typ kolumny boolean. Dla tego pola istnieje indeks utworzony za pomocą ActiveRecord ORM.

CREATE INDEX index_table_on_auto_renew ON table USING btree (auto_renew);

Gdy pytam pola o wartość logiczną, PG używa indeksu zgodnie z oczekiwaniami.

EXPLAIN for: SELECT "table".* FROM "table"  WHERE "table"."auto_renew" = 'f'
                                          QUERY PLAN
----------------------------------------------------------------------------------------------
 Bitmap Heap Scan on table  (cost=51.65..826.50 rows=28039 width=186)
   Filter: (NOT auto_renew)
   ->  Bitmap Index Scan on index_domains_on_auto_renew  (cost=0.00..44.64 rows=2185 width=0)
         Index Cond: (auto_renew = false)
(4 rows)

Gdy wartością jest NULL, używane jest skanowanie sekwencyjne.

EXPLAIN for: SELECT "table".* FROM "table"  WHERE "table"."auto_renew" IS NULL
                           QUERY PLAN
----------------------------------------------------------------
 Seq Scan on table  (cost=0.00..1094.01 rows=25854 width=186)
   Filter: (auto_renew IS NULL)
(2 rows)

Jestem ciekawy przyczyny tego wyboru.

Simone Carletti
źródło

Odpowiedzi:

19

Zasadniczo col IS NULLjest możliwym kandydatem do (domyślnego) wyszukiwania indeksu b-drzewa. Podręcznik :

Można również użyć warunku IS NULLlub IS NOT NULLw kolumnie indeksu z indeksem B-drzewa.

Aby uzyskać dowód, wyłącz skanowanie sekwencyjne (tylko w sesji testowej!):

SET enable_seqscan = OFF;

Przytaczam instrukcję tutaj :

enable_seqscan (boolean)

Włącza lub wyłącza użycie przez planer zapytań typów planów skanowania sekwencyjnego. Niemożliwe jest całkowite wyeliminowanie skanowania sekwencyjnego, ale wyłączenie tej zmiennej zniechęca planistę do korzystania z niej, jeśli dostępne są inne metody. Domyślnie jest włączony.

Następnie spróbuj ponownie:

EXPLAIN ANALYZE SELECT * FROM tbl WHERE auto_renew IS NULL;

Prawdopodobnie spowoduje to skanowanie indeksu map bitowych, które będzie wolniejsze niż skanowanie sekwencyjne na stole.

Zresetuj lub zamknij sesję (ustawienie to sesja lokalna).

RESET enable_seqscan;

Indeksy na booleankolumnach są użyteczne tylko w niektórych przypadkach. Planista używa indeksu tylko wtedy, gdy oczekuje, że będzie on szybszy. Obliczenia są oparte na ustawieniach kosztów i statystykach gromadzonych przez ANALYZE. Jeśli znaczna część tabeli odpowiada twojemu warunkowi (około 5% lub więcej, to zależy), zwykle zamiast tego wykonuje się pełne skanowanie tabeli.

Pozostawia to rzadką wartość w booleankolumnie jako jedynego przydatnego kandydata na zwykły indeks. Zwykle bardziej efektywne jest utworzenie (bardziej wyspecjalizowanego) indeksu częściowego w tym celu - który jest tańszy w utrzymaniu, mniejszy, szybszy i stosowany łatwiej, jeśli warunki zapytania są zgodne.

Jeśli masz wiele zapytań szukających wierszy, auto_renew IS NULLa wielkość NULLliter nie jest zbyt powszechna (i / lub potrzebujesz określonej kolejności sortowania), ten indeks pomoże szybko znaleźć / posortować te wiersze:

CREATE INDEX index_tbl_tbl_id_auto_renew_null ON tbl (tbl_id)
WHERE auto_renew IS NULL;

Warunek indeksu częściowego należy powtórzyć w WHEREklauzuli zapytania mniej więcej dokładnie, aby planista zapytań zdał sobie sprawę, że indeks ma zastosowanie.

Zindeksowana kolumna ( tbl_id) jest dowolnym wyborem. Ważną częścią jest WHEREklauzula. Ten konkretny indeks byłby najbardziej efektywny w przypadku zapytań z ORDER BY tbl_iddodatkowym filtrem lub dołączenia tbl_id. Możesz ustawić indeks wielokolumnowy . Kolumny logiczne są często bardziej przydatne w połączeniu z innymi.

Poza tym: ORM to kule, które regularnie nie wykorzystują pełnego potencjału RDBMS.

Erwin Brandstetter
źródło
Świetna odpowiedź, dziękuję Erwin. Przykro mi, że nie mogę dwukrotnie głosować za tym.
Simone Carletti