Seq Scan inesperado cuando se realiza una consulta contra boolean con valor NULL

10

Tengo una columna de base de datos llamada auto_reviewdonde está el tipo de columna boolean. Hay un índice para ese campo, creado utilizando ActiveRecord ORM.

CREATE INDEX index_table_on_auto_renew ON table USING btree (auto_renew);

Cuando consulto el campo para un valor booleano, PG usa el índice como se esperaba.

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)

Cuando el valor es NULL, se usa una exploración secuencial.

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)

Tengo curiosidad por saber la razón detrás de esta elección.

Simone Carletti
fuente

Respuestas:

19

En general, col IS NULLes un posible candidato para una búsqueda de índice b-tree (predeterminado). El manual :

Además, una condición IS NULLo IS NOT NULLen una columna de índice se puede usar con un índice de árbol B.

Para obtener pruebas, deshabilite los análisis secuenciales (¡solo en una sesión de prueba!):

SET enable_seqscan = OFF;

Cito el manual aquí :

enable_seqscan (boolean)

Habilita o deshabilita el uso del planificador de consultas de los tipos de planes de exploración secuenciales. Es imposible suprimir por completo los análisis secuenciales, pero desactivar esta variable desalienta al planificador de usar uno si hay otros métodos disponibles. El valor predeterminado está activado.

Vuelva a intentarlo:

EXPLAIN ANALYZE SELECT * FROM tbl WHERE auto_renew IS NULL;

Esto probablemente dará como resultado una exploración de índice de mapa de bits que es más lenta que una exploración secuencial en la tabla.

Restablezca o cierre la sesión (la configuración es local de sesión).

RESET enable_seqscan;

Los índices en booleancolumnas solo son útiles en ciertos casos. El planificador solo usa un índice si espera que sea más rápido. Los cálculos se basan en su configuración de costos y las estadísticas recopiladas por ANALYZE. Si una parte considerable de la tabla coincide con su condición (alrededor del 5% o más, depende), generalmente es más rápido hacer un escaneo completo de la tabla.

Esto deja el valor raro en una booleancolumna como el único candidato útil para un índice simple. Por lo general, es más eficiente crear un índice parcial (más especializado) para esto, que es más barato de mantener, más pequeño, más rápido y se usa más fácilmente si la condición de consulta coincide.

Si tiene muchas consultas en busca de filas auto_renew IS NULLy el NULLcaso no es muy común (y / o necesita un cierto orden de clasificación), este índice ayudaría a encontrar / ordenar estas filas rápidamente:

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

La condición del índice parcial debe repetirse en la WHEREcláusula de una consulta más o menos exactamente para que el planificador de consultas se dé cuenta de que el índice es aplicable.

La columna indexada ( tbl_id) es una elección arbitraria. La parte importante es la WHEREcláusula. Este índice en particular sería más efectivo para consultas con ORDER BY tbl_ido un filtro adicional o unirse tbl_id. Podría convertirlo en un índice de varias columnas . Las columnas booleanas son a menudo más útiles en combinación con otras.

Aparte: los ORM son muletas que regularmente no logran aprovechar todo el potencial de su RDBMS.

Erwin Brandstetter
fuente
Brillante respuesta, gracias Erwin. Estoy triste porque no puedo votarlo dos veces.
Simone Carletti