sqlpostgresqlexplainperformance

EXPLAIN (ANALYZE, BUFFERS) en PostgreSQL: leer el plan real de la consulta

Como leer EXPLAIN (ANALYZE, BUFFERS): filas y tiempo reales frente a estimados, loops, y la brecha que delata estadisticas obsoletas o un indice ausente.

2 min de lecturaReferencesql · postgresql · explain · performance · index

Un EXPLAIN simple muestra solo el plan y las estimaciones del planificador. EXPLAIN (ANALYZE, BUFFERS) ejecuta de verdad la consulta e informa filas, tiempo y accesos a paginas reales. La brecha entre lo estimado y lo real suele ser lo que delata el problema.

Valores reales frente a estimados

Cada nodo del plan muestra dos pares de numeros. Los estimados son cost y rows (lo que el planificador supuso antes de ejecutar). Los reales son actual time y rows (lo que de verdad ocurrio).

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 5;

Una linea de nodo tipica se ve asi:

Seq Scan on orders  (cost=0.00..1934.00 rows=12 width=40)
                    (actual time=0.40..18.7 rows=9 loops=1)
  • cost=0.00..1934.00 es una estimacion en unidades arbitrarias: arranque y recorrido completo.
  • rows=12 se esperaba, actual ... rows=9 es lo que realmente volvio.
  • actual time=0.40..18.7 son milisegundos hasta la primera y hasta la ultima fila.

Lee el plan de abajo hacia arriba y de dentro hacia fuera: los nodos hoja se ejecutan primero.

Loops: multiplica por el numero de pasadas

En un nested loop el nodo interno se ejecuta muchas veces, y su actual time/rows se informa por cada pasada. Multiplica por loops para obtener el total.

EXPLAIN (ANALYZE, BUFFERS)
SELECT u.email, o.amount
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.country = 'DE';

Si el Index Scan interno muestra actual time=0.02 rows=3 loops=5000, no son 0.02 ms sino unos 100 ms en total y 15000 filas. Gotcha: un tiempo por pasada minimo con un loops grande es la forma mas comun de subestimar un nodo caro.

La brecha entre estimado y real

Una gran diferencia entre rows estimado y real es la senal clave. El planificador elige su estrategia segun la estimacion; si la estimacion miente, elige un mal plan.

ANALYZE orders;  -- reconstruir estadisticas

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE status = 'paid' AND created_at >= '2026-01-01';
  • Estimacion equivocada por decenas de veces suele indicar estadisticas obsoletas: ejecuta ANALYZE.
  • La brecha sobrevive al ANALYZE: probablemente predicados correlacionados; ayudan las estadisticas extendidas (CREATE STATISTICS) o un indice.
  • Una sobreestimacion hace que el planificador desperdicie esfuerzo en un Seq Scan en vez de un Index Scan.

BUFFERS: shared hit y read

BUFFERS muestra el trabajo de memoria en cada nodo. shared hit son paginas halladas en cache; shared read son paginas leidas de disco (lento).

Seq Scan on orders  (actual time=0.4..210 rows=2000000 loops=1)
  Buffers: shared hit=128 read=16234
  • shared hit es un acierto en shared_buffers, barato.
  • shared read es un fallo de cache, una lectura fisica; muchos read significan un nodo pesado en I/O.
  • Buffers te dice si la consulta fue lenta por disco o por un mal plan.

Un Seq Scan que deberia ser Index Scan

El caso practico principal: una consulta filtra por una columna selectiva, pero ves un Seq Scan con un read enorme y tiempo real alto.

-- antes: Seq Scan, lee toda la tabla para 9 filas
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 5;

CREATE INDEX idx_orders_user ON orders (user_id);

-- despues: Index Scan, lectura puntual
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 5;

Tras el indice, el Seq Scan se convierte en Index Scan y Buffers: shared read cae de miles a unas pocas.

  • MySQL: EXPLAIN ANALYZE (8.0+) da actual time y loops; no hay buffers al estilo PostgreSQL, asi que mira Handler_read* y el profiler.
  • ClickHouse: en vez de un plan con datos reales usas EXPLAIN para la estructura y los logs del sistema (query_log) para las metricas de lectura reales.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador