Pregunta 1Cuando SCAN perf_sales aparece en un plan de ejecución, ¿cuál de estos es el significado correcto?
Leer planes de ejecución — EXPLAIN QUERY PLAN
Recorre el vocabulario que aparece en EXPLAIN QUERY PLAN. Verás en qué se diferencian los barridos completos de tabla de las búsquedas por índice, cuándo aparece un B-tree temporal para ordenar, qué aspecto tiene un Index-Only Scan (índice cubriente), cómo se lee una subconsulta correlacionada en el plan y qué significa MATERIALIZE — todo activando y desactivando índices.
El dataset de este artículo — perf_sales (50 000 filas de ventas)
Cuando quieres saber si una consulta es rápida o lenta, lo primero que hay que mirar es el plan de ejecución (el query plan).
Dada una sentencia SQL, la base de datos decide internamente "qué tablas leer, en qué orden y cómo".
La herramienta que imprime ese procedimiento como texto es EXPLAIN QUERY PLAN.
Antepón esta palabra clave a una consulta y verás los pasos de ejecución en lugar de los resultados reales.
SCAN y SEARCH — barridos completos y búsquedas por índice
Las dos primeras palabras que hay que aprender en un plan de ejecución son SCAN y SEARCH.
SCAN perf_sales significa leer cada fila desde el principio hasta el final de la tabla (un barrido completo).
Cuando filtras por una columna que no tiene índice, la base de datos no sabe qué filas coinciden, así que revisa las 50 000 filas una por una.
Mientras tanto, SEARCH perf_sales USING INDEX index_name (column=?) significa seguir el índice para aterrizar directamente sobre las filas coincidentes (una búsqueda por índice).
Crea un índice con CREATE INDEX y los filtros sobre esa columna dejan de recorrer cada fila — el plan cambia de SCAN a SEARCH ... USING INDEX.
-- Filtra por region: sin índice, así que lee cada fila
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE region = 'East';
--> SCAN perf_sales
-- Construye un índice sobre region y mira el mismo plan
DROP INDEX IF EXISTS ix_region;
CREATE INDEX ix_region ON perf_sales(region);
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE region = 'East';
--> SEARCH perf_sales USING INDEX ix_region (region=?)
USE TEMP B-TREE — un área temporal para ordenar
USE TEMP B-TREE FOR ORDER BY (un B-tree temporal) significa que la columna del ORDER BY no tiene índice, así que la base de datos está construyendo un área de trabajo temporal para ordenar los resultados.
Las 50 000 filas van a esa área temporal para reordenarse, así que el coste crece con el número de filas.
Si hay un índice utilizable para la ordenación, esta línea desaparece del plan y se usa directamente el orden ya existente del índice.
-- Top 10 por amount ascendente: sin índice, hace falta un área temporal para ordenar
EXPLAIN QUERY PLAN
SELECT sale_id, amount FROM perf_sales ORDER BY amount LIMIT 10;
--> SCAN perf_sales
--> USE TEMP B-TREE FOR ORDER BY
-- Construye un índice sobre amount y la ordenación puede reusar su orden directamente
DROP INDEX IF EXISTS ix_amount;
CREATE INDEX ix_amount ON perf_sales(amount);
EXPLAIN QUERY PLAN
SELECT sale_id, amount FROM perf_sales ORDER BY amount LIMIT 10;
--> SEARCH perf_sales USING INDEX ix_amount
-- USE TEMP B-TREE desaparece del plan
USING COVERING INDEX — un índice que no toca la tabla
USING COVERING INDEX (un Index-Only Scan (índice cubriente), también llamado covering index en inglés) significa que cada columna que la consulta necesita está contenida dentro del índice, así que la base de datos puede construir el resultado solo a partir del índice sin leer la tabla en sí.
Un índice es una copia pequeña solo de las columnas que extraes de la tabla, así que saltarse la lectura de la tabla es lo que lo hace más rápido.
Es un paso más allá de SEARCH ... USING INDEX — cuando el plan muestra USING COVERING INDEX, estás obteniendo la lectura más eficiente.
-- Índice solo sobre emp_id -> encuentra filas vía el índice, luego lee amount de la tabla
DROP INDEX IF EXISTS ix_emp;
CREATE INDEX ix_emp ON perf_sales(emp_id);
EXPLAIN QUERY PLAN
SELECT emp_id, amount FROM perf_sales WHERE emp_id = 7;
--> SEARCH perf_sales USING INDEX ix_emp (emp_id=?)
-- (también se lee la tabla)
-- Índice compuesto sobre (emp_id, amount) -> todas las columnas necesarias están en el índice, sin lectura de la tabla
DROP INDEX IF EXISTS ix_emp_amount;
CREATE INDEX ix_emp_amount ON perf_sales(emp_id, amount);
EXPLAIN QUERY PLAN
SELECT emp_id, amount FROM perf_sales WHERE emp_id = 7;
--> SEARCH perf_sales USING COVERING INDEX ix_emp_amount (emp_id=?)
Planes de subconsultas — CORRELATED y MATERIALIZE
Las consultas con subconsultas tienen su propio vocabulario dedicado en el plan.
CORRELATED SCALAR SUBQUERY significa que el valor de la subconsulta depende de la fila externa actual, así que se vuelve a ejecutar una vez por cada fila externa.
Con 50 000 filas externas, la subconsulta puede ejecutarse 50 000 veces — cuando esta palabra aparece en el plan, es candidata a una reescritura.
Mientras tanto, MATERIALIZE significa que el resultado de una subconsulta o CTE se calcula una vez, se guarda en una tabla temporal y se reutiliza.
Es una optimización que paga el coste de computación una vez para un resultado intermedio al que haces referencia varias veces.
Cuál de estas dos palabras aparece te dice si la subconsulta se está "computando cada vez" o si está "computada una vez y reutilizada".
-- Subconsulta escalar correlacionada: vuelve a obtener "el amount máximo de esta region" por cada fila externa
EXPLAIN QUERY PLAN
SELECT s.sale_id, s.amount
FROM perf_sales s
WHERE s.amount = (
SELECT MAX(amount) FROM perf_sales x WHERE x.region = s.region
)
LIMIT 10;
--> aparece una línea CORRELATED SCALAR SUBQUERY
-- Una tabla derivada (subconsulta en la cláusula FROM) se computa una vez
EXPLAIN QUERY PLAN
SELECT t.region, t.s FROM (
SELECT region, SUM(amount) AS s FROM perf_sales GROUP BY region
) t
WHERE t.s > 100000000;
--> puede aparecer una línea MATERIALIZE
Cómo se mapea esto a otros RDBMS — MySQL / PostgreSQL / Oracle
El vocabulario de plan de este artículo viene del EXPLAIN QUERY PLAN de SQLite, pero MySQL / PostgreSQL / Oracle muestran los mismos conceptos con nombres distintos.
Empieza revisando cómo cada base de datos produce un plan de ejecución, luego compara la chuleta de la terminología de planes — podrás leer planes en otra base de datos sin mucho aprendizaje adicional.
Cómo imprimir un plan de ejecución en cada base de datos
- SQLite: antepón
EXPLAIN QUERY PLANa la consulta (p. ej.EXPLAIN QUERY PLAN SELECT ...;). No ejecuta la consulta, solo imprime el plan. ElEXPLAINpelado muestra el bytecode de bajo nivel, así que para lectura humana quieresEXPLAIN QUERY PLAN - MySQL: antepón
EXPLAINa la consulta (p. ej.EXPLAIN SELECT ...;). MySQL 8.0+ también tieneEXPLAIN ANALYZE SELECT ...;para el tiempo real de ejecución y los recuentos reales de filas. Para salida JSON, usaEXPLAIN FORMAT=JSON SELECT ...; - PostgreSQL: antepón
EXPLAINa la consulta (p. ej.EXPLAIN SELECT ...;). Eso es solo el plan estimado. Para reales, usaEXPLAIN ANALYZE SELECT ...;, y para información detallada de buffers de E/S, usaEXPLAIN (ANALYZE, BUFFERS) SELECT ...; - Oracle: un patrón de dos pasos —
EXPLAIN PLAN FOR <query>;guarda el plan en una tabla interna, ySELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);lo imprime. Para visualización rápida, la opción de sesiónSET AUTOTRACE ONtambién funciona
Chuleta de la terminología de planes
| SQLite (EXPLAIN QUERY PLAN) | MySQL (EXPLAIN) | PostgreSQL (EXPLAIN) | Oracle (EXPLAIN PLAN) |
|---|---|---|---|
SCAN (barrido completo de tabla) | type=ALL | Seq Scan | TABLE ACCESS FULL |
SEARCH ... USING INDEX | type=ref / range / eq_ref | Index Scan / Bitmap Index Scan | INDEX RANGE SCAN / INDEX UNIQUE SCAN |
USING COVERING INDEX | Extra: Using index | Index Only Scan | INDEX FAST FULL SCAN |
USE TEMP B-TREE FOR ORDER BY | Extra: Using filesort | Sort | SORT ORDER BY |
USE TEMP B-TREE FOR GROUP BY | Extra: Using temporary; Using filesort | HashAggregate / GroupAggregate | HASH GROUP BY / SORT GROUP BY |
CORRELATED SCALAR SUBQUERY | DEPENDENT SUBQUERY | SubPlan (correlated) | subconsulta correlacionada (dentro de FILTER) |
MATERIALIZE | Extra: Using temporary | Materialize / CTE Scan | transformación de tabla temp (TEMP TABLE TRANSFORMATION) |
La redacción difiere, pero lo que la base de datos está haciendo internamente es esencialmente lo mismo. El objetivo de este artículo es ser capaz de leer "¿está leyendo cada fila, está acotando con un índice, necesita un área de trabajo para ordenar, está ejecutando una subconsulta una y otra vez?" en la salida de EXPLAIN de cada base de datos.
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Por qué USING COVERING INDEX es más eficiente que USING INDEX en un plan de ejecución?
Pregunta 3Cuando CORRELATED SCALAR SUBQUERY aparece en un plan de ejecución, ¿qué interpretación es la más precisa?