sql >> Base de Datos >  >> RDS >> PostgreSQL

Postgres que no usa índice cuando el escaneo de índice es una opción mucho mejor

Escaneo de índice (solo) --> Escaneo de índice de mapa de bits --> Escaneo secuencial

Para algunas filas, vale la pena ejecutar un escaneo de índice. Si hay suficientes páginas de datos visibles para todos (=aspiradas lo suficiente y sin demasiada carga de escritura simultánea) y el índice puede proporcionar todos los valores de columna necesarios, entonces se usa un escaneo de índice más rápido. Dado que se espera que se devuelvan más filas (mayor porcentaje de la tabla y dependiendo de la distribución de datos, frecuencias de valor y ancho de fila), es más probable encontrar varias filas en una página de datos. Entonces vale la pena cambiar a escaneos de índice de mapa de bits. (O para combinar múltiples índices distintos). Una vez que se debe visitar un gran porcentaje de páginas de datos de todos modos, es más económico ejecutar un escaneo secuencial, filtrar las filas excedentes y omitir la sobrecarga de los índices por completo.

El uso de índices se vuelve (mucho) más barato y más probable cuando acceder a páginas de datos en orden aleatorio no es (mucho) más costoso que acceder a ellas en orden secuencial. Ese es el caso cuando se usa SSD en lugar de discos giratorios, o incluso más cuanto más se almacena en caché en RAM, y los parámetros de configuración respectivos random_page_cost y effective_cache_size se configuran en consecuencia.

En su caso, Postgres cambia a un escaneo secuencial, esperando encontrar rows=263962 , eso ya es el 3 % de toda la mesa. (Mientras que solo rows=47935 se encuentran realmente, ver más abajo.)

Más en esta respuesta relacionada:

  • ¿Consulta de PostgreSQL eficiente en la marca de tiempo usando escaneo de índice o índice de mapa de bits?

Cuidado con forzar los planes de consulta

No puede forzar un determinado método de planificación directamente en Postgres, pero puede crear otro los métodos parecen extremadamente costosos para fines de depuración. Consulte Configuración del método Planner en el manual.

SET enable_seqscan = off (como se sugiere en otra respuesta) hace eso con escaneos secuenciales. Pero eso está destinado solo para fines de depuración en su sesión. no use esto como una configuración general en producción a menos que sepa exactamente lo que está haciendo. Puede forzar planes de consulta ridículos. El manual:

Estos parámetros de configuración proporcionan un método rudimentario para influir en los planes de consulta elegidos por el optimizador de consultas. Si el plan predeterminado elegido por el optimizador para una consulta en particular no es óptimo, un temporal La solución es usar uno de estos parámetros de configuración para obligar al optimizador a elegir un plan diferente. Las mejores formas de mejorar la calidad de los planes elegidos por el optimizador incluyen ajustar las constantes de costo del planificador (consulte la Sección 19.7.2), ejecutar ANALYZE manualmente, aumentando el valor de default_statistics_target parámetro de configuración y aumentar la cantidad de estadísticas recopiladas para columnas específicas usando ALTER TABLE SET STATISTICS .

Eso ya es la mayor parte del consejo que necesita.

  • Evite que PostgreSQL a veces elija un mal plan de consulta

En este caso particular, Postgres espera 5 o 6 veces más visitas a email_activities.email_recipient_id de lo que realmente se encuentran:

estimado rows=227007 frente a actual ... rows=40789
estimado rows=263962 frente a actual ... rows=47935

Si ejecuta esta consulta con frecuencia, le conviene tener ANALYZE mire una muestra más grande para obtener estadísticas más precisas sobre la columna en particular. Su tabla es grande (~ 10 millones de filas), así que haga eso:

ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000;  -- max 10000, default 100

Luego ANALYZE email_activities;

Medida de último recurso

En muy raro casos, puede recurrir a forzar un índice con SET LOCAL enable_seqscan = off en una transacción separada o en una función con su propio entorno. Me gusta:

CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
  RETURNS bigint AS
$func$
   SELECT COUNT(DISTINCT a.email_recipient_id)
   FROM   email_activities a
   WHERE  a.email_recipient_id IN (
      SELECT id
      FROM   email_recipients
      WHERE  email_campaign_id = $1
      LIMIT  $2)       -- or consider query below
$func$  LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;

La configuración solo se aplica al ámbito local de la función.

Advertencia: Esto es solo una prueba de concepto. Incluso esta intervención manual mucho menos radical podría morderte a la larga. Cardinalidades, frecuencias de valores, su esquema, configuraciones globales de Postgres, todo cambia con el tiempo. Vas a actualizar a una nueva versión de Postgres. El plan de consulta que fuerza ahora, puede convertirse en una muy mala idea más tarde.

Y, por lo general, esto es solo una solución para un problema con su configuración. Mejor encuéntralo y arréglalo.

Consulta alternativa

Falta información esencial en la pregunta, pero esta consulta equivalente es probablemente más rápida y es más probable que use un índice en (email_recipient_id ) - cada vez más para un LIMIT más grande .

SELECT COUNT(*) AS ct
FROM  (
   SELECT id
   FROM   email_recipients
   WHERE  email_campaign_id = 1607
   LIMIT  43000
   ) r
WHERE  EXISTS (
   SELECT FROM email_activities
   WHERE  email_recipient_id = r.id);