bitlvl
ES

Visualizador de planes EXPLAIN

Ejecuta EXPLAIN (ANALYZE, BUFFERS) (o FORMAT JSON) en PostgreSQL, o EXPLAIN ANALYZE en MySQL 8, y pega la salida. Cada nodo muestra su propio tiempo, su porcentaje del total, las filas estimadas frente a las reales y los buffers; la lista de hallazgos dice qué es lento y qué suele arreglarlo. El plan se queda en tu navegador.

Ejemplos:

Los resultados y mensajes de esta herramienta se muestran en inglés.

Leer un plan

Un plan es un árbol de operaciones; la raíz devuelve el resultado y cada nodo extrae filas de sus hijos. EXPLAIN solo muestra las estimaciones del planificador (unidades de coste y filas esperadas); EXPLAIN ANALYZE ejecuta realmente la consulta y añade tiempos y recuentos de filas reales, que es lo que hace legible un plan. BUFFERS añade cuántas páginas de 8 kB vinieron de la caché (hit) o del disco (read). El camino habitual por un plan: encontrar el nodo con mayor tiempo exclusivo y luego ver por qué está ahí: una estimación de filas errónea por encima, un índice que falta o un ajuste de memoria.

Los números de un nodo

CampoSignificado
cost=0.29..8.31Estimación del planificador en unidades arbitrarias: coste de devolver la primera fila .. coste de devolver todas. Solo comparable dentro de un mismo plan.
rows=Filas estimadas por loop. La contrapartida real también es por loop; multiplica por loops para el total.
actual time=0.05..12.3Milisegundos hasta la primera fila .. última fila, por loop, incluidos los hijos. La herramienta resta los hijos para mostrar el tiempo propio de cada nodo.
loops=Cuántas veces se ejecutó el nodo. El lado interno de un nested loop se ejecuta una vez por fila externa; bajo un Gather es igual al número de workers.
Buffers: shared hit=.. read=..Páginas encontradas en shared_buffers frente a leídas del SO/disco. Las lecturas son adonde va el tiempo de E/S; track_io_timing lo muestra como I/O Timings.
Rows Removed by FilterFilas que el nodo leyó y luego descartó: la señal más clara de que falta un índice.

Los cinco problemas habituales

  1. Estimaciones erróneas. Si un nodo espera 10 filas y recibe 100.000, el planificador eligió un nested loop o un sort que solo tenía sentido para 10. Ejecuta ANALYZE; para predicados sobre columnas correlacionadas crea estadísticas extendidas; con CTE y funciones el planificador no tiene ni idea y recurre a suposiciones.
  2. Sequential scan con un filtro selectivo. Bien en tablas pequeñas. En las grandes, la solución es un índice sobre las columnas filtradas, idealmente que cubra el WHERE y el ORDER BY.
  3. Spills. Sort external merge, Hash Batches > 1, HashAggregate en disco, bitmaps lossy: la operación no cupo en work_mem. Subirlo para la sesión o la consulta es barato; subirlo globalmente se multiplica por las conexiones.
  4. Nested loop sobre un scan. El lado interno se ejecuta una vez por fila externa; si es un Seq Scan o un Materialize de un conjunto grande, el tiempo explota. El lado interno necesita un índice, o hay que permitir al planificador un hash join.
  5. Index-only scans que no lo son. Heap Fetches cercano al número de filas significa que el visibility map está desactualizado: VACUUM de la tabla.

Obtener planes

-- PostgreSQL: la forma más útil
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, SETTINGS, FORMAT JSON) SELECT …;
-- para escrituras, dentro de una transacción que deshaces
BEGIN; EXPLAIN (ANALYZE, BUFFERS) UPDATE …; ROLLBACK;
-- registrar planes lentos automáticamente: auto_explain
LOAD 'auto_explain'; SET auto_explain.log_min_duration = '200ms'; SET auto_explain.log_analyze = on;

-- MySQL 8.0.18+
EXPLAIN ANALYZE SELECT …;          -- árbol con tiempos reales
EXPLAIN FORMAT=JSON SELECT …;      -- estimaciones con detalles de coste

Preguntas

¿Por qué los tiempos de los nodos no suman el total?

El tiempo real de cada nodo incluye a sus hijos, así que la herramienta muestra el tiempo exclusivo (nodo menos hijos). Los workers paralelos se ejecutan a la vez, por lo que un nodo bajo Gather con loops=3 se cuenta una vez, no tres; los lados internos de los nested loops se multiplican por loops. El tiempo de planificación y el de triggers se listan aparte.

¿Qué es un buen plan?

Uno en el que las estimaciones están dentro de un factor diez de los valores reales, ningún nodo vuelca a disco, el nodo más caro es el que esperas (la tabla grande que de verdad tienes que leer) y los buffers leídos del disco son pocos comparados con los hits. Para un plan así, la lista de hallazgos está vacía.

¿Admite tablas particionadas y planes paralelos?

Sí: Append / Merge Append sobre particiones, Gather y Gather Merge con Workers Planned/Launched, agregados Partial y Finalize. Los desgloses por worker (VERBOSE) se analizan pero se muestran como parte del nodo.

¿Puedo pegar un plan de pgAdmin, DataGrip o psql con bordes de tabla?

Sí. Las líneas se limpian de los caracteres de caja de psql, de la cabecera QUERY PLAN y del pie «(n rows)». El JSON copiado de una GUI como una sola cadena entrecomillada debe desentrecomillarse primero.

¿Cómo es el soporte de MySQL?

El árbol EXPLAIN ANALYZE de MySQL 8 tiene la misma forma que un plan de texto de PostgreSQL y está totalmente soportado; EXPLAIN FORMAT=JSON (solo estimaciones) se mapea sobre el mismo árbol: los tipos de acceso se convierten en tipos de scan, nested_loop en un nodo join y los indicadores de filesort / tabla temporal en hallazgos. ANALYZE FORMAT=JSON de MariaDB aún no está soportado.