Varredura Seq inesperada ao fazer consultas em relação ao booleano com valor NULL

10

Eu tenho uma coluna de banco de dados chamada auto_reviewonde está o tipo de coluna boolean. Há um índice para esse campo, criado usando o ActiveRecord ORM.

CREATE INDEX index_table_on_auto_renew ON table USING btree (auto_renew);

Quando eu consulto o campo em busca de um valor booleano, o PG usa o índice conforme o esperado.

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)

Quando o valor é NULL, uma varredura seqüencial é usada.

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)

Estou curioso para saber o motivo por trás dessa escolha.

Simone Carletti
fonte

Respostas:

19

Geralmente, col IS NULLé um possível candidato a uma pesquisa de índice b-tree (padrão). O manual :

Além disso, uma condição IS NULLou IS NOT NULLem uma coluna de índice pode ser usada com um índice de árvore B.

Para obter provas, desative as verificações sequenciais (apenas em uma sessão de teste!):

SET enable_seqscan = OFF;

Cito o manual aqui :

enable_seqscan (boolean)

Ativa ou desativa o uso pelo planejador de consultas de tipos de plano de varredura seqüencial. É impossível suprimir totalmente as varreduras seqüenciais, mas desativar essa variável desencoraja o planejador de usar uma se houver outros métodos disponíveis. O padrão está ativado.

Em seguida, tente novamente:

EXPLAIN ANALYZE SELECT * FROM tbl WHERE auto_renew IS NULL;

Provavelmente, isso resultará em uma verificação de índice de bitmap mais lenta que uma verificação seqüencial na tabela.

Redefina ou feche a sessão (a configuração é local da sessão).

RESET enable_seqscan;

Os índices nas booleancolunas são úteis apenas em certos casos. O planejador usa apenas um índice se espera que seja mais rápido. Os cálculos são baseados nas suas configurações de custo e nas estatísticas coletadas por ANALYZE. Se uma parte considerável da tabela corresponder à sua condição (cerca de 5% ou mais, depende), normalmente será mais rápido fazer uma verificação completa da tabela.

Isso deixa o valor raro em uma booleancoluna como o único candidato útil para um índice simples. E normalmente é mais eficiente criar um índice parcial (mais especializado) para isso - que é mais barato de manter, menor, mais rápido e usado com mais facilidade se a condição da consulta corresponder.

Se você tiver muitas consultas procurando linhas com auto_renew IS NULLe o NULLcaso não for muito comum (e / ou precisar de uma determinada ordem de classificação), esse índice ajudaria a encontrar / classificar essas linhas rapidamente:

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

A condição do índice parcial deve ser repetida na WHEREcláusula de uma consulta mais ou menos exatamente para fazer o planejador de consultas perceber que o índice é aplicável.

A coluna indexada ( tbl_id) é uma escolha arbitrária. A parte importante é a WHEREcláusula. Esse índice específico seria mais eficaz para consultas ORDER BY tbl_idou um filtro adicional ou associação tbl_id. Você pode torná-lo um índice de várias colunas . As colunas booleanas geralmente são mais úteis em combinação com outras.

Além: ORMs são muletas que falham regularmente em obter todo o potencial do seu RDBMS.

Erwin Brandstetter
fonte
Resposta brilhante, obrigado Erwin. Estou triste por não poder votar duas vezes.
Simone Carletti