PostgreSQL puede usar un índice para id < 11 y un recorrido secuencial para id > 11 porque la primera condición coincide con muy pocas filas, mientras que la segunda coincide con casi toda la tabla. Ambos operadores admiten índices B-tree; el planificador compara sus costes estimados.
Esta guía ayuda a un desarrollador que trabaja en un Mac a reproducir esa elección con filas de artículos sintéticas, leer los planes y comprobar si la diferencia requiere atención. Sigue la documentación actual recuperada de PostgreSQL 18 y la documentación actual de node-postgres. Comprueba la versión de tu servidor antes de comparar los detalles de los planes; los ejemplos no garantizan planes idénticos en distintas versiones o máquinas. PostgreSQL: Uso de EXPLAIN
¿Por qué el mismo índice puede producir distintas opciones de recorrido?
Un índice B-tree admite tanto comparaciones de menor que como de mayor que. Cambiar el sentido de la comparación no hace que el índice deje de ser utilizable. Lo que cambia es el rango de valores y, por tanto, la cantidad de filas que el planificador espera recuperar. PostgreSQL: Tipos de índices
Considera una tabla ilustrativa que contiene exactamente 100,000 filas de artículos, con ID enteros consecutivos del 1 al 100,000:
| Predicado | Filas coincidentes | Proporción de la tabla |
|---|---|---|
id < 11 |
10 | 0.01% |
id > 11 |
99,989 | 99.989% |
id = 11 |
1 | 0.001% |
Estas cantidades se deducen de los datos sintéticos de entrada, no de mediciones de una base de datos existente. Ninguno de los dos rangos incluye el ID 11. Un índice puede localizar eficientemente los diez ID coincidentes del primer rango. En el segundo, de todos modos identificaría casi todas las filas.
Un recorrido normal del índice en PostgreSQL obtiene entradas del índice y las filas correspondientes del almacenamiento principal de la tabla, llamado heap. Esas filas del heap pueden estar distribuidas en muchas páginas. Cuando se necesita casi todas las páginas, leer directamente la tabla puede costar menos que recorrer el índice y obtener las filas a las que hace referencia. PostgreSQL: Recorridos solo de índice e índices de cobertura
Por tanto, el número 11 no tiene un significado especial para el planificador. Con datos diferentes, la condición de mayor que podría corresponder al rango más estrecho. PostgreSQL estima la fracción de filas coincidentes, conocida como selectividad, usando estadísticas de la tabla. El tamaño numérico del límite por sí solo no determina esa fracción. PostgreSQL: Estadísticas que utiliza el planificador
¿Cómo reproduces la comparación en un Mac?
Necesitas una conexión existente a PostgreSQL y tener disponible el comando psql en la Terminal de tu Mac. La instalación queda fuera de este ejercicio. Abre tu conexión habitual a la base de datos con psql; acepta opciones de conexión y proporciona un indicador interactivo de SQL. Ejecuta los siguientes bloques en una sesión nueva. PostgreSQL: psql
La configuración crea una tabla temporal que contiene únicamente texto sintético de artículos. Las tablas temporales desaparecen cuando termina la sesión. La clave primaria entera normal crea automáticamente un índice B-tree único, por lo que no hace falta un segundo índice en id. PostgreSQL: CREATE TABLE
SHOW server_version;
CREATE TEMP TABLE article_scan_demo (
id integer PRIMARY KEY,
title text NOT NULL,
body text NOT NULL
);
INSERT INTO article_scan_demo (id, title, body)
SELECT
g,
'Article ' || g,
repeat('Sample article text. ', 20)
FROM generate_series(1, 100000) AS s(g);
ANALYZE article_scan_demo;
SELECT
count(*) AS total_rows,
count(*) FILTER (WHERE id < 11) AS below_11,
count(*) FILTER (WHERE id > 11) AS above_11
FROM article_scan_demo;
El generate_series entero de PostgreSQL incluye ambos extremos con el paso predeterminado de uno. Por tanto, la consulta de conteo debería devolver 100,000 filas en total, 10 por debajo de 11 y 99,989 por encima de 11. PostgreSQL: Funciones que devuelven conjuntos
Ejecuta ANALYZE después de cargar las filas para que el planificador tenga estadísticas sobre la tabla poblada. Este comando recopila estadísticas; cumple una función distinta de la opción de medición de ejecución de EXPLAIN ANALYZE. PostgreSQL: ANALYZE
A continuación, compara las dos consultas que devuelven filas completas:
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)
SELECT * FROM article_scan_demo WHERE id < 11;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)
SELECT * FROM article_scan_demo WHERE id > 11;
Estos comandos ejecutan cada consulta e informan sobre su plan y los recuentos reales de filas, sin devolver todas las filas de artículos seleccionadas. BUFFERS añade la actividad de bloques; TIMING OFF desactiva la medición del tiempo de los nodos individuales. Esto sirve para estudiar la ejecución, pero no mide el tiempo que se necesita para enviar todos los datos de artículos coincidentes a una aplicación de escritorio. PostgreSQL: EXPLAIN
Con la configuración habitual, espera un plan basado en índices para el rango estrecho y un recorrido secuencial para el rango amplio. Tómalos como expectativas: PostgreSQL puede elegir un plan de mapa de bits u otro plan aplicable según sus estimaciones. Se revisó el SQL de este ejemplo, pero durante su preparación no había un servidor PostgreSQL disponible para ejecutarlo; los cálculos de recuento de filas se comprobaron por separado.
¿Qué partes del plan explican la decisión?
Empieza por el nodo de recorrido, su rows estimado y su rows real. Para la primera consulta, busca Index Scan con un Index Cond, o un Bitmap Index Scan debajo de un Bitmap Heap Scan. Para la segunda, busca Seq Scan con un Filter. Un plan de mapa de bits sigue usando un índice; organiza las ubicaciones de las filas coincidentes antes de obtener las páginas del heap. PostgreSQL: Uso de EXPLAIN
Luego comprueba estas observaciones:
| Observación en este ejemplo | Qué investigar |
|---|---|
| Las estimaciones coinciden aproximadamente con las 10 y las 99,989 filas reales | Las distintas cantidades de filas coincidentes explican las diferentes elecciones. |
| Las filas estimadas difieren mucho de las reales | Actualiza las estadísticas antes de cambiar la configuración del planificador. |
| Ambos predicados usan recorridos secuenciales en una muestra mucho más pequeña | El tamaño de la tabla puede hacer más barato el recorrido directo. |
| Aparece un plan de mapa de bits | Compara sus estimaciones de filas y el trabajo en el heap como otra alternativa basada en índices. |
Los costes del planificador son estimaciones en unidades arbitrarias, no milisegundos. Compáralos como parte del modelo de PostgreSQL, en lugar de interpretar un coste de 100 como una duración. Como el ejemplo utiliza tablas temporales, la actividad de búferes aparece en los bloques locales; los búferes de tablas e índices normales aparecen en los bloques compartidos. PostgreSQL: EXPLAIN
Si las estimaciones te sorprenden, continúa en la misma sesión con esta consulta de diagnóstico:
SELECT
schemaname,
n_distinct,
histogram_bounds,
correlation
FROM pg_stats
WHERE schemaname = (
SELECT nspname
FROM pg_namespace
WHERE oid = pg_my_temp_schema()
)
AND tablename = 'article_scan_demo'
AND attname = 'id';
El histograma describe la distribución de valores utilizada para estimar rangos. La correlación describe cuánto sigue el orden lógico de la columna el orden físico de las filas; los valores cercanos a cualquiera de los extremos pueden hacer que el acceso al heap mediante índices parezca menos costoso. Ninguno de estos campos establece un porcentaje universal a partir del cual PostgreSQL deba cambiar de tipo de recorrido. PostgreSQL: pg_stats
¿Cómo comparas las consultas mediante node-postgres?
node-postgres envía el texto de la consulta y los valores de los parámetros a PostgreSQL, donde se realiza la planificación. Su API de consultas acepta SQL seguido de un array de valores, así que ambas comparaciones pueden usar el mismo valor límite de 11. node-postgres: Consultas
Para una reproducción independiente en Node.js, necesitas una instalación operativa de Node.js/npm y la configuración de una conexión existente a PostgreSQL. En un directorio de proyecto nuevo, ejecuta npm init -y y luego npm install pg. Guarda el programa completo de abajo como compare-scans.mjs y ejecuta node compare-scans.mjs. El cliente usa opciones de entorno de PostgreSQL, como PGHOST, PGPORT y PGDATABASE; utiliza las opciones correspondientes a tu base de datos local existente. node-postgres: Conexión
import pg from 'pg';
const client = new pg.Client();
try {
await client.connect();
await client.query(`
CREATE TEMP TABLE article_scan_demo (
id integer PRIMARY KEY,
title text NOT NULL,
body text NOT NULL
)
`);
await client.query(`
INSERT INTO article_scan_demo (id, title, body)
SELECT
g,
'Article ' || g,
repeat('Sample article text. ', 20)
FROM generate_series(1, 100000) AS s(g)
`);
await client.query('ANALYZE article_scan_demo');
const queries = [
'EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT * FROM article_scan_demo WHERE id < $1',
'EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT * FROM article_scan_demo WHERE id > $1'
];
for (const text of queries) {
const result = await client.query(text, [11]);
console.log(text);
console.log(
result.rows.map(row => row['QUERY PLAN']).join('\n')
);
}
} finally {
await client.end();
}
El programa crea su propia tabla de muestra y mantiene todas las operaciones en una sola conexión. Imprime ambos planes y cierra la conexión al terminar. Se comprobó la sintaxis de JavaScript con Node.js; durante la preparación no se ejecutó contra una base de datos. Si falla la configuración de la conexión, resuelve el problema antes de interpretar el comportamiento del recorrido.
¿Pueden cambiar la comparación las sentencias preparadas con nombre?
Añadir un name a la configuración de una consulta de node-postgres crea una sentencia preparada con nombre en cada conexión. Esto introduce otra distinción: PostgreSQL puede utilizar un plan personalizado basado en los valores de los parámetros o un plan genérico compartido entre ejecuciones. node-postgres: Consultas, PostgreSQL: PREPARE
Con la política automática predeterminada de PostgreSQL 18, las cinco primeras ejecuciones usan planes personalizados. Después, PostgreSQL compara el coste estimado de un plan genérico con el coste medio de los planes personalizados; no cambia automáticamente a un plan genérico en la sexta ejecución. PostgreSQL: PREPARE
Si una sentencia con nombre de una aplicación se comporta de forma distinta de una prueba con SQL literal, examina esa sentencia en la misma conexión mediante EXPLAIN EXECUTE. Un plan genérico muestra símbolos de parámetros, mientras que un plan personalizado sustituye los valores proporcionados. Mantén las dos condiciones de comparación en sentencias con nombre separadas porque su texto SQL es distinto. PostgreSQL: PREPARE
¿Cuándo deberías cambiar la consulta o la configuración del planificador?
Primero conserva el objetivo real de la consulta. Sustituir SELECT * por SELECT id cambia los datos que PostgreSQL debe recuperar. Si un índice adecuado contiene todas las columnas necesarias, es posible un recorrido solo de índice. Evitar las búsquedas en el heap también depende del mapa de visibilidad, así que un plan solo de índice no garantiza un acceso nulo al heap. PostgreSQL: Recorridos solo de índice e índices de cobertura
Si la tarea requiere todos los artículos coincidentes, conserva ese requisito al comparar los planes. Si solo requiere un pequeño subconjunto ordenado, prueba en su lugar la consulta real con ORDER BY y LIMIT: el ordenamiento y la terminación anticipada pueden cambiar el plan preferido. PostgreSQL: Uso de EXPLAIN
Después de comprobar las estadísticas, puedes comparar un plan alternativo en la sesión SQL existente:
BEGIN;
SET LOCAL enable_seqscan = off;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)
SELECT * FROM article_scan_demo WHERE id > 11;
ROLLBACK;
Esta opción desaconseja los recorridos secuenciales, pero no puede suprimirlos por completo. Usa el resultado como comparación diagnóstica. PostgreSQL también modela el acceso secuencial y no secuencial a las páginas mediante seq_page_cost y random_page_cost. Cambiar sus valores relativos puede modificar las elecciones del plan, pero la documentación recomienda evaluar la configuración de costes en toda la carga de consultas, en lugar de basarse en unos pocos experimentos. PostgreSQL: Planificación de consultas
En esta tabla sintética, la comparación decisiva es entre 10 filas coincidentes y 99,989. Comprueba la consulta exacta, actualiza las estadísticas, compara las filas estimadas y reales, y examina el comportamiento de los planes preparados cuando corresponda. Esos pasos permiten determinar si el recorrido secuencial se ajusta al trabajo solicitado y aportan pruebas para cualquier cambio posterior.
Fuentes
- Uso de EXPLAIN — PostgreSQL
- Tipos de índices — PostgreSQL
- Estadísticas que utiliza el planificador — PostgreSQL
- Recorridos solo de índice e índices de cobertura — PostgreSQL
- psql — PostgreSQL
- CREATE TABLE — PostgreSQL
- Funciones que devuelven conjuntos — PostgreSQL
- ANALYZE — PostgreSQL
- EXPLAIN — PostgreSQL
- pg_stats — PostgreSQL
- Consultas — node-postgres
- Conexión — node-postgres
- PREPARE — PostgreSQL
- Planificación de consultas — PostgreSQL