El Planificador de Consultas
El planificador de consultas de PostgreSQL, a veces llamado optimizador, es el componente que decide cómo se ejecutará realmente una consulta antes de que se lea una sola fila.
No sigue un libro de reglas fijo como lo haría un intérprete ingenuo; en cambio, estima el costo de muchas estrategias de ejecución candidatas y elige la que cree que es la más barata, por eso PostgreSQL se describe como un optimizador basado en costos.
Todo lo que este planificador decide, desde el tipo de escaneo hasta el orden de las uniones y el algoritmo de unión, en última instancia se remonta a una sola entrada: cuántas filas espera en cada paso, y esa expectativa proviene de las estadísticas recopiladas sobre sus datos.
Resumen
- El planificador estima el costo de las estrategias de ejecución candidatas utilizando estadísticas de recuento de filas y elige la más barata, en lugar de seguir reglas fijas.
- Por Qué Importa: Un planificador que trabaja con estadísticas precisas elige buenos planes automáticamente; un planificador que trabaja con estadísticas obsoletas o ingenuas elige malos planes, a menudo de forma silenciosa.
- Conceptos Clave: selectividad, estimación de cardinalidad, modelo de costos,
pg_stats,ANALYZE, GUCs del planificador. - Cuándo Usar: Razonar sobre por qué una consulta eligió un plan sobre otro, ajustar
work_memo constantes de costo, o diagnosticar una mala estimación sistemática en muchas consultas. - Limitaciones / Compensaciones: El modelo de costos del planificador es una aproximación calibrada con constantes configurables, y asume la independencia de las columnas a menos que se le indique lo contrario a través de estadísticas extendidas.
- Temas Relacionados: Lectura de planes
EXPLAIN, diseño de índices, autovacuum y autoanalyze, estadísticas extendidas.
Fundamentos
Cada consulta que el planificador considera comienza con una pregunta: ¿cuántas filas sobrevivirán a este filtro, esta unión, este agregado?
Ese número se llama estimación de cardinalidad, y es el valor más importante que produce el planificador, porque todos los cálculos de costos posteriores dependen de él.
Las estimaciones de cardinalidad provienen de la selectividad, la fracción de las filas de una tabla que se espera que coincidan con una condición, que el planificador calcula utilizando estadísticas almacenadas en la vista del sistema pg_stats.
Esas estadísticas no se derivan escaneando toda la tabla en cada consulta; se capturan periódicamente mediante el comando ANALYZE, que muestrea un subconjunto de filas y crea un resumen estadístico compacto.
Ese resumen típicamente incluye una lista de los valores más comunes y sus frecuencias, un histograma de la distribución de los valores restantes, un recuento estimado de valores distintos y la fracción de filas que son nulas.
Para un filtro de igualdad como status = 'shipped', el planificador verifica si 'shipped' aparece en la lista de valores más comunes y utiliza su frecuencia registrada directamente si es así, o recurre a una estimación genérica basada en el recuento de valores distintos si no.
Para un filtro de rango como created_at > now() - interval '7 days', el planificador consulta en cambio el histograma para estimar qué fracción de filas caen por encima de ese límite.
Una vez que el planificador tiene una estimación de filas para un escaneo de tabla base, introduce ese número en una fórmula de costos que tiene en cuenta las lecturas de páginas y el trabajo de CPU por fila, y ese costo resultante es lo que ve impreso en cada nodo en la salida de EXPLAIN.
El planificador no es omnisciente sobre la intención de su consulta; solo es tan bueno como el modelo estadístico que se le proporciona, y ese modelo es una aproximación deliberadamente compacta, no una copia completa de sus datos.
Mecánica e Interacciones
La verdadera complejidad del planificador emerge una vez que una consulta combina múltiples condiciones o múltiples tablas, porque las selectividades deben combinarse en una única estimación conjunta.
La suposición predeterminada es la independencia de las columnas: si status = 'shipped' coincide con el 5% de las filas y country = 'US' coincide con el 20% de las filas, el planificador asume que su combinación coincide aproximadamente con el 1% de las filas mediante una simple multiplicación.
Esa suposición de independencia a menudo es incorrecta en esquemas reales, donde columnas como country y currency, o status y shipped_at IS NULL, están naturalmente correlacionadas, y multiplicar sus selectividades subestima o sobreestima la verdadera selectividad conjunta.
Las estadísticas extendidas, creadas con CREATE STATISTICS, existen específicamente para reparar esto registrando la correlación observada real, la dependencia o los recuentos de valores distintos conjuntos entre columnas nombradas.
Para las uniones, el planificador extiende el mismo enfoque de estimar-luego-costear a través de cada orden de unión y algoritmo de unión viables que está dispuesto a considerar, utilizando programación dinámica para evitar la fuerza bruta de cada permutación una vez que el número de tablas crece.
Pondera una unión de bucle anidado, que repite un escaneo interno por cada fila externa, contra una unión hash, que construye una tabla hash en memoria una vez, contra una unión de fusión, que requiere que ambos lados estén pre-ordenados por la clave de unión, y cada una tiene una forma de costo diferente dependiendo de los recuentos de filas estimados en ambos lados.
Aquí es precisamente donde una mala estimación de cardinalidad se vuelve peligrosa en lugar de simplemente inconveniente: una subestimación del recuento de filas puede llevar al planificador a elegir un bucle anidado que cree que solo iterará unas pocas veces, cuando en realidad itera cientos de miles.
Varios parámetros de configuración, colectivamente llamados GUCs del planificador, dan forma a esta decisión sin tocar las estadísticas mismas; random_page_cost ajusta qué tan costosa parece la E/S aleatoria en relación con la E/S secuencial, y work_mem limita cuánta memoria puede usar una operación de ordenación o hash antes de desbordarse a disco.
El trabajador de autoanalyze de Autovacuum evita que las estadísticas se vuelvan obsoletas automáticamente, activando un ANALYZE fresco una vez que han cambiado suficientes filas en una tabla, aunque las cargas masivas y las eliminaciones masivas pueden superar ese umbral y dejar al planificador trabajando con una imagen desactualizada mientras tanto.
El planificador también trata las subconsultas, las expresiones comunes de tabla (CTE) y las vistas en gran medida insertándolas y re-optimizándolas en contexto, en lugar de tratarlas como cajas negras opacas, por lo que el plan de una CTE puede verse diferente según cómo se haga referencia a ella en otra parte de la consulta.
-- La suposición de selectividad conjunta en miniatura: sin estadísticas extendidas,
-- el planificador multiplica estas dos selectividades como si fueran independientes
-- a pesar de que los pedidos de 'US' son desproporcionadamente 'shipped'.
CREATE STATISTICS orders_country_status_stats (dependencies)
ON country, status FROM orders;
ANALYZE orders;Consideraciones Avanzadas y Aplicaciones
A escala, el comportamiento del planificador se convierte en un problema de sistemas, no solo en una curiosidad por consulta, porque miles de consultas por segundo dependen del mismo modelo estadístico que se mantiene fresco.
Las tablas particionadas complican aún más la estimación, ya que el planificador debe combinar las decisiones de poda de particiones con estadísticas por partición, y una partición con datos inusualmente sesgados puede dominar silenciosamente el costo de una consulta que toca todo el conjunto particionado.
Los índices multicolumna también interactúan con las estimaciones del planificador, porque un índice puede hacer que un plan sea lo suficientemente barato como para seleccionarlo incluso cuando la estimación de filas subyacente es solo aproximadamente correcta, siempre que el índice evite un escaneo completo de las filas descartadas.
Las sentencias preparadas introducen un modo de planificación distinto: PostgreSQL puede planificar genéricamente, sin conocimiento de los valores reales de los parámetros, después de suficientes ejecuciones con un plan personalizado, intercambiando una pequeña cantidad de precisión por plan para la capacidad de omitir la replanificación en cada llamada.
Las cargas de trabajo impulsadas por extensiones agregan sus propias arrugas estadísticas; los índices de vecino más cercano aproximado de pgvector, por ejemplo, dependen de que el planificador estime correctamente un escaneo de índice frente a un escaneo secuencial para la búsqueda de similitud, lo que depende de estimaciones de filas precisas tanto como los escaneos B-tree ordinarios.
La mala estimación sistemática en un esquema completo, en lugar de una sola consulta, suele ser un signo de un problema estructural: una tabla donde los umbrales de autoanalyze son demasiado gruesos para su volumen de escritura, o una carga de trabajo construida completamente sobre predicados de expresión que las estadísticas de columnas simples no pueden describir.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Estadísticas predeterminadas de una sola columna | Configuración cero, automática a través de autoanalyze | Asume independencia de columnas | La mayoría de las tablas con columnas de filtro no relacionadas |
Mayor default_statistics_target / SET STATISTICS por columna | Detalle más fino de histograma y MCV | ANALYZE más lento, filas de catálogo más grandes | Columnas fuertemente sesgadas utilizadas en filtros frecuentes |
Estadísticas extendidas (dependencies, ndistinct, mcv) | Modela la correlación real de columnas | Debe crearse y reanalizarse explícitamente | Pares de columnas correlacionadas conocidos que causan malas estimaciones conjuntas |
Ajuste manual de GUCs del planificador (random_page_cost, work_mem) | Cambia el modelo de costos para que coincida con el hardware real | Efecto global o por rol, no específico de la consulta | Almacenamiento respaldado por SSD u operaciones de ordenación/hash con memoria limitada |
Conceptos Erróneos Comunes
- "El planificador escanea la tabla para saber cuántas filas coinciden." - Nunca lo hace en tiempo de planificación; estima a partir de un resumen estadístico muestreado periódicamente en
pg_stats. - "Más índices siempre significan mejores planes." - Un índice solo ayuda si el modelo de costos del planificador, impulsado por estimaciones de filas precisas, lo juzga más barato que las alternativas para esa consulta específica.
- "El planificador trata cada condición de la cláusula WHERE de forma independiente y correcta." - Asume la independencia por defecto, lo que subestima o sobreestima la selectividad siempre que las columnas están correlacionadas.
- "ANALYZE lee toda la tabla." -
ANALYZEmuestrea un número limitado de filas controlado pordefault_statistics_target, no la tabla completa, lo que lo mantiene rápido incluso en relaciones muy grandes. - "Una consulta lenta siempre significa estadísticas faltantes." - A veces las estadísticas son precisas y el plan es genuinamente el más barato disponible; la solución real es un cambio de esquema o índice, no
ANALYZE. - "Los GUCs del planificador como random_page_cost arreglan las malas estimaciones." - Los GUCs remodelan el modelo de costos aplicado a una estimación de filas dada; no hacen nada para corregir una estimación que simplemente está mal.
Preguntas Frecuentes
¿Qué significa que PostgreSQL use un planificador "basado en costos"?
El planificador asigna un costo numérico a cada forma candidata de ejecutar una consulta y elige la más baja, en lugar de seguir un conjunto fijo de reglas.
Ese costo se deriva de las estimaciones del recuento de filas y las constantes de costo configurables, no de la ejecución real de las alternativas.
¿De dónde provienen realmente las estimaciones de filas del planificador?
Provienen de pg_stats, una vista de catálogo poblada por el comando ANALYZE.
ANALYZE muestrea la tabla en lugar de escanearla por completo, luego registra los valores más comunes, un histograma, recuentos de valores distintos y la fracción de nulos por columna.
¿Por qué dos consultas muy similares obtendrían planes muy diferentes?
Los diferentes valores literales pueden afectar diferentes partes de las estadísticas de una columna, como un valor más común frente a un bucket de histograma, produciendo diferentes estimaciones de selectividad.
La planificación genérica de sentencias preparadas también puede divergir de un plan personalizado creado para un conjunto específico de parámetros.
¿Cómo decide el planificador entre una unión de bucle anidado, una unión hash y una unión de fusión?
Calcula el costo de cada algoritmo utilizando los recuentos de filas estimados en ambos lados de la unión y elige el más barato.
Los bucles anidados favorecen un lado externo pequeño, las uniones hash favorecen un lado sin ordenar pero que cabe en memoria, y las uniones de fusión favorecen entradas que ya están ordenadas o son fáciles de ordenar.
¿Por qué la independencia de las columnas se considera una "suposición predeterminada" en lugar de una garantía?
Multiplicar las selectividades para combinar múltiples condiciones solo es matemáticamente correcto cuando las columnas son estadísticamente independientes.
Los esquemas reales frecuentemente tienen columnas correlacionadas, que es exactamente la brecha que las estadísticas extendidas están diseñadas para cerrar.
¿Qué cambia realmente cuando ejecuto CREATE STATISTICS para estadísticas extendidas?
PostgreSQL comienza a rastrear una estadística conjunta, como la dependencia funcional o el recuento combinado de valores distintos, entre las columnas nombradas.
Esa estadística conjunta se popula en el próximo ANALYZE y se utiliza automáticamente siempre que el planificador estima la selectividad para esas columnas juntas.
¿Aumentar `default_statistics_target` siempre mejora la calidad del plan?
Puede mejorar la precisión para columnas sesgadas al registrar más valores más comunes y cubos de histograma más finos.
También hace que ANALYZE sea más lento y aumenta el tamaño del catálogo, por lo que es mejor aplicarlo selectivamente a las columnas que realmente lo necesitan.
¿Con qué frecuencia se actualiza la imagen estadística del planificador?
El trabajador de autoanalyze de Autovacuum activa un ANALYZE fresco una vez que el recuento estimado de cambios de una tabla cruza un umbral.
Las cargas masivas o las eliminaciones masivas pueden superar ese umbral, por lo que a menudo vale la pena hacer un ANALYZE manual explícitamente después de un gran cambio de datos.
¿Cuál es la relación entre el planificador y EXPLAIN?
EXPLAIN es la herramienta que expone el plan elegido por el planificador y sus estimaciones subyacentes de costos y filas para su inspección.
El planificador en sí se ejecuta en cada consulta independientemente de si alguna vez mira EXPLAIN.
¿Pueden los GUCs del planificador como random_page_cost arreglar un plan que elige el índice incorrecto?
Pueden cambiar la preferencia relativa del modelo de costos entre E/S secuencial y aleatoria, lo que a veces influye en una decisión límite.
No pueden corregir una estimación de filas fundamentalmente incorrecta, que es un problema de estadísticas en lugar de un problema de constantes de costos.
¿Por qué las subconsultas y las CTE a veces obtienen planes diferentes dependiendo de cómo se utilicen?
El planificador generalmente inserta y re-optimiza subconsultas y CTEs en el contexto de la consulta circundante en lugar de tratarlas como cajas negras fijas.
Eso significa que el mismo texto de CTE puede planificarse de manera diferente dependiendo de los predicados y uniones que lo rodean.
¿Es el planificador de consultas lo mismo que el ejecutor de consultas?
No, el planificador decide qué plan ejecutar, y el ejecutor es el componente separado que realmente recorre ese plan y produce filas.
EXPLAIN por sí solo solo ejercita el planificador; EXPLAIN ANALYZE ejercita ambos.
Relacionados
- Estadísticas Básicas - los campos de pg_stats y la mecánica del histograma referenciados aquí
- ANALYZE y Autoanalyze - cómo y cuándo se actualizan realmente las estadísticas
- Estadísticas Extendidas - cómo cerrar directamente la suposición de independencia de columnas
- GUCs del Planificador - las constantes de costos que dan forma a la selección del plan
- Cómo Funciona EXPLAIN - lectura del árbol de planes en el que se alimentan las estimaciones de esta página
Versiones de Stack: Esta página fue escrita para PostgreSQL 18.4 (línea estable 18, línea de mantenimiento 17); la arquitectura del planificador basado en costos y el modelo
pg_statsdescritos aquí son estables en las versiones principales recientes.