Qué muestra EXPLAIN
EXPLAIN muestra el plan de una consulta: los pasos que DuckDB sigue para contestarla, de abajo arriba. Leerlo te dice qué tabla se recorre, qué filtros se aplican y dónde se agrupa.
Una consulta lenta casi nunca se arregla a ciegas. Primero se mira el plan y se ve qué paso trabaja con más filas.
Al terminar esta lección
Nota
Puedo leer un plan con EXPLAIN y explicar por qué una consulta cuesta menos que otra.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Plan de consulta | los pasos con que el motor contesta una consulta | |
| EXPLAIN | la palabra que pide el plan en vez del resultado | |
| Escaneo | un paso que lee una tabla entera | Seq Scan |
| Filtro | un paso que descarta las filas que no cumplen la condición | |
| Filas estimadas | las filas que el motor calcula que pasarán por un paso | ~10 rows |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
EXPLAIN muestra los pasos de la consulta
Escribe EXPLAIN delante de una consulta. No la ejecuta: devuelve el plan, un árbol de pasos. Se lee de abajo arriba. Abajo se leen las tablas y arriba sale el resultado.
Cada paso tiene un nombre. Seq Scan lee una tabla, Hash Join une dos y Hash Group By agrupa. Bajo cada nombre aparecen las columnas que usa y, tras un ~, las filas que el motor espera.
Ejemplo resuelto: los pasos de un plan
| Orden | Paso | Qué hace | Qué se ve en él |
|---|---|---|---|
| 1 | Seq Scan de rides | Lee viajes: solo station_id y minutes | Filters: minutes > 20 |
| 2 | Seq Scan de stations | Lee estaciones: solo station_id y name | Una condición que DuckDB añade solo |
| 3 | Hash Join | Une los viajes con las estaciones | station_id = station_id |
| 4 | Hash Group By | Agrupa por name y suma minutes | Aggregates: sum(minutes) |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pruébalo: el plan de una suma
-- El plan de una suma sobre una sola columna.
explain select sum(minutes) from rides;
-- Cambia sum(minutes) por * y mira las columnas que lee el escaneo.Cuanto menos lee un escaneo, menos cuesta
En la lección 1 viste que una tabla por columnas lee solo las columnas que nombras. El plan lo confirma: el Seq Scan lista las columnas que va a leer.
Una suma de minutes lee una columna. Un select * lee las cuatro. El dibujo cambia con la versión y con las estadísticas, así que fíjate en los nombres de los pasos y no en cada línea.
Ejemplo resuelto: columnas que lee el escaneo
| Consulta | Columnas que lista el Seq Scan |
|---|---|
| select sum(minutes) from rides | minutes |
| select * from rides | ride_id, station_id, minutes, paid_eur |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pruébalo: el plan de un join
-- El plan de un join con un filtro, que se escribe después.
explain
select name, sum(minutes) as total
from rides join stations using (station_id)
where minutes > 20
group by name;
-- Busca los pasos Seq Scan, Hash Join y Hash Group By.Filtrar antes de unir reduce el trabajo
Un filtro antes de un JOIN hace que se unan menos filas. DuckDB suele mover los filtros hacia abajo por su cuenta. En el plan anterior, minutes > 20 aparece dentro del escaneo de rides, aunque se escribió en el WHERE.
Pero filtrar antes no siempre da lo mismo. Con un LEFT JOIN, un WHERE sobre la tabla de la derecha borra las filas sin pareja.
Ejemplo resuelto: dos formas de filtrar
| name | long_rides con WHERE | long_rides con el filtro antes |
|---|---|---|
| Estación Norte | 1 | 1 |
| Parque Central | 1 | 1 |
| Plaza Mayor | 1 | 1 |
| Puerto | no aparece | 0 |
| Universidad | 2 | 2 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que mover una condición entre WHERE y una subconsulta siempre da el mismo resultado. Con LEFT JOIN no es así: un WHERE sobre la tabla derecha descarta las estaciones sin viajes. Si quieres conservarlas, filtra antes de unir.
Pruébalo: filtrar antes de unir
-- Primero se filtran los viajes; después se unen a las estaciones.
select s.name, count(r.ride_id) as long_rides
from stations as s
left join (select * from rides where minutes > 20) as r
on r.station_id = s.station_id
group by s.name
order by s.name;
-- Mueve la condición a un WHERE de fuera y mira qué fila desaparece.