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);