Saltar al contenido

lección 6

EXPLAIN: radiografía de cómo piensa tu base de datos

Qué es un plan de ejecución, cómo leer un EXPLAIN en DuckDB, los operadores reales y por qué tu query tarda.

55 min

Tu query tarda 47 segundos. El usuario se queja. Tu jefe te mira. Tienes dos opciones: (1) reescribir la query a ciegas esperando que mejore, o (2) ENTENDER por qué tarda y arreglar exactamente lo que está mal. La diferencia entre un junior y un senior es que el senior SIEMPRE elige la opción 2. Y la herramienta para eso es EXPLAIN.

EXPLAIN es como una radiografía de tu query. Le dices a la base de datos "no ejecutes esto, solo dime CÓMO lo ejecutarías", y te devuelve un plan detallado: qué tablas lee, en qué orden, cómo las combina, si usa índices o no, cuántas filas estima procesar. Es la escena del crimen, y tú eres el detective.

### La analogía: el GPS vs ir a ciegas

Cuando introduces una dirección en el GPS, este calcula la ruta antes de que empieces a conducir. Te dice "toma la autopista A6, después la salida 23, gira a la derecha". Si tarda mucho, puedes ver POR QUÉ: "ah, hay un atasco en la M-30 y está evitándola". EXPLAIN es el GPS de tu base de datos: te muestra la ruta que va a seguir para obtener tus resultados, ANTES de ejecutarla.

### EXPLAIN en DuckDB y PostgreSQL

1-- DuckDB: plan textual
2EXPLAIN
3SELECT c.ciudad, SUM(p.importe) AS total
4FROM clientes c
5JOIN pedidos p ON p.cliente_id = c.id
6GROUP BY c.ciudad;
7
8-- DuckDB: plan con tiempos reales (ejecuta la query)
9EXPLAIN ANALYZE
10SELECT c.ciudad, SUM(p.importe) AS total
11FROM clientes c
12JOIN pedidos p ON p.cliente_id = c.id
13GROUP BY c.ciudad;
14
15-- Nota: en PostgreSQL se usa EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
16-- pero en DuckDB basta con EXPLAIN o EXPLAIN ANALYZE.

EXPLAIN muestra el plan sin ejecutar. EXPLAIN ANALYZE ejecuta y muestra tiempos reales.

### Lectura del plan: los operadores de DuckDB

Un plan de ejecución es un árbol de operadores. Cada operador hace una cosa: leer datos, filtrar, ordenar, agrupar, combinar. Los datos fluyen de abajo hacia arriba. DuckDB usa nombres propios (no los de PostgreSQL), así que esto es lo que verás en tu terminal:

  • SEQ_SCAN — Lee la tabla columna a columna. DuckDB es columnar: solo lee las columnas que tu SELECT necesita, no la fila entera. Además usa "zone maps" (min/max por bloque de filas) para saltarse bloques enteros que no cumplen el WHERE.
  • FILTER — Aplica condiciones WHERE. Descarta filas que no cumplen el predicado.
  • HASH_JOIN — Combina dos tablas creando una tabla hash en memoria de la más pequeña y buscando coincidencias. El join más habitual en DuckDB.
  • PIECEWISE_MERGE_JOIN — Combina tablas ya ordenadas por la clave de join. Lo ves en joins con rango (BETWEEN, >=) donde el hash no aplica.
  • HASH_GROUP_BY — Agrupa resultados usando una tabla hash. Es lo que hace GROUP BY internamente.
  • ORDER_BY — Ordena filas. Equivale al SORT de PostgreSQL.
  • PROJECTION — Selecciona y renombra columnas. Aparece en TODOS los planes, es el operador que aplica tu SELECT.

INDEX_SCAN no existe en DuckDB. Es columnar y analítico: los índices B-tree no ayudan aquí. Lo que en PostgreSQL acelera un índice (filtrar pocas filas de una tabla grande), en DuckDB lo hacen los zone maps sin crear nada. Lo verás en la lección siguiente.

Si vienes de tutoriales de PostgreSQL, aquí tienes la traducción:

  • PostgreSQL Seq Scan → DuckDB SEQ_SCAN (misma idea, pero DuckDB solo lee las columnas pedidas)
  • PostgreSQL Index Scan → no existe en DuckDB (zone maps cumplen esa función para analítica)
  • PostgreSQL Hash Aggregate → DuckDB HASH_GROUP_BY
  • PostgreSQL Sort → DuckDB ORDER_BY
  • PostgreSQL Merge Join → DuckDB PIECEWISE_MERGE_JOIN
  • PostgreSQL Filter → DuckDB FILTER (igual)
  • Sin equivalente en PostgreSQL → DuckDB PROJECTION (aparece siempre)
Sin índice: la DB lee millones de filas. Con índice: va directo. La diferencia puede ser 1000x.

### Cómo leer un plan de DuckDB paso a paso

1-- Ejecuta esto en tu ecommerce.duckdb:
2EXPLAIN ANALYZE
3SELECT
4 c.ciudad,
5 COUNT(*) AS pedidos,
6 SUM(p.importe)::INTEGER AS total
7FROM clientes c
8JOIN pedidos p ON p.cliente_id = c.id
9WHERE p.estado = 'completado'
10 AND p.fecha >= '2024-01-01'
11GROUP BY c.ciudad
12ORDER BY total DESC;
13
14-- El plan se lee de ABAJO hacia ARRIBA:
15-- 1. SEQ_SCAN de "pedidos" (filtra estado y fecha)
16-- 2. SEQ_SCAN de "clientes"
17-- 3. HASH_JOIN entre ambas (clave: cliente_id)
18-- 4. HASH_GROUP_BY ciudad
19-- 5. ORDER_BY total DESC
20-- 6. PROJECTION (selecciona columnas finales)

Lee el plan de abajo a arriba: los datos fluyen desde las tablas base hasta el resultado.

Consejo de senior: no necesitas entender cada detalle del plan. Busca las líneas rojas: SEQ_SCAN en tablas grandes con filtros muy selectivos (¿DuckDB pudo saltarse bloques con zone maps?), estimaciones de filas altas (¿se puede filtrar antes?), ORDER_BY en millones de filas (¿se puede evitar?). El 80% de los problemas de rendimiento se ven a simple vista en el plan.

### Señales de alerta en un plan de ejecución

  • SEQ_SCAN en tabla con millones de filas + filtro muy selectivo → en PostgreSQL necesitaría índice. En DuckDB, comprueba si los zone maps ya lo resuelven (suelen hacerlo)
  • Estimación de filas muy diferente a la realidad (EXPLAIN vs EXPLAIN ANALYZE) → estadísticas desactualizadas
  • ORDER_BY con "external merge" → la ordenación no cabe en memoria, usa disco (lento)
  • Nested Loop Join en tablas grandes → debería ser HASH_JOIN o PIECEWISE_MERGE_JOIN
  • Múltiples escaneos de la misma tabla → quizás una CTE materializada ayude

### EXPLAIN ANALYZE vs EXPLAIN

EXPLAIN solo muestra el PLAN estimado — no ejecuta la query. Es rápido y seguro. EXPLAIN ANALYZE ejecuta la query completa y te da tiempos REALES y conteos REALES de filas. Es más informativo pero tarda lo que tarda la query. Para queries que sabes que son lentas, empieza con EXPLAIN (sin ANALYZE) para no esperar.

EXPLAIN ANALYZE realmente EJECUTA la query. Si tu query es un INSERT, UPDATE o DELETE, EXPLAIN ANALYZE modificará datos. Envuélvelo en una transacción que hagas rollback, o usa solo EXPLAIN sin ANALYZE para queries que modifiquen datos.

Regla de oro del rendimiento: MIDE antes de optimizar. He visto equipos pasar semanas "optimizando" queries que tardaban 200ms mientras ignoraban la que tardaba 45 segundos. EXPLAIN ANALYZE te dice exactamente dónde está el cuello de botella. Sin datos, no optimices.

## ejercicios

[01]

Tu primer EXPLAIN

Ejecuta EXPLAIN ANALYZE sobre una query que une clientes con pedidos, filtra por ciudad y agrupa por canal. Identifica: ¿qué tipo de scan hace? ¿Qué tipo de join usa? ¿Cuántas filas procesa?

Cargando editor...
[02]

Comparar dos queries equivalentes

Hay dos formas de obtener los clientes con más de 5 pedidos: con HAVING y con una subconsulta + WHERE. Ejecuta EXPLAIN ANALYZE en ambas y determina cuál es más eficiente.

Cargando editor...
[03]

Mover el filtro para mejorar rendimiento

Esta query es lenta porque filtra DESPUÉS del join. Reescríbela para filtrar ANTES y compara los planes con EXPLAIN ANALYZE.

Cargando editor...

Regístrate para guardar tu progreso.

## comentarios

Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.

Inicia sesión para comentar y responder.

cargando comentarios...