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;
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.
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 5;
CREATE INDEX idx_orders_user ON orders (user_id);
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.
Un
EXPLAINsimple 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
costyrows(lo que el planificador supuso antes de ejecutar). Los reales sonactual timeyrows(lo que de verdad ocurrio).EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 5;Una linea de nodo tipica se ve asi:
cost=0.00..1934.00es una estimacion en unidades arbitrarias: arranque y recorrido completo.rows=12se esperaba,actual ... rows=9es lo que realmente volvio.actual time=0.40..18.7son 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/rowsse informa por cada pasada. Multiplica porloopspara 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 Scaninterno muestraactual 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 unloopsgrande es la forma mas comun de subestimar un nodo caro.La brecha entre estimado y real
Una gran diferencia entre
rowsestimado 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';ANALYZE.ANALYZE: probablemente predicados correlacionados; ayudan las estadisticas extendidas (CREATE STATISTICS) o un indice.BUFFERS: shared hit y read
BUFFERSmuestra el trabajo de memoria en cada nodo.shared hitson paginas halladas en cache;shared readson paginas leidas de disco (lento).shared hites un acierto enshared_buffers, barato.shared reades un fallo de cache, una lectura fisica; muchosreadsignifican un nodo pesado en I/O.Bufferste 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 Scancon unreadenorme 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 Scanse convierte enIndex ScanyBuffers: shared readcae de miles a unas pocas.EXPLAIN ANALYZE(8.0+) daactual timeyloops; no hay buffers al estilo PostgreSQL, asi que miraHandler_read*y el profiler.EXPLAINpara la estructura y los logs del sistema (query_log) para las metricas de lectura reales.