El plan de ejecución es una herramienta fundamental para optimizar consultas SQL. Sin comprenderlo, es prácticamente imposible realizar ajustes de rendimiento efectivos. ¿Qué es exactamente? Es la ruta que el optimizador de MySQL decide seguir para ejecutar una sentencia SQL.
Para obtenerlo, se utiliza la palabra clave EXPLAIN antes de la consulta. También es posible combinarlo con SHOW WARNINGS para obtener información adicional, como por qué no se utilizó cierto índice.
EXPLAIN SELECT * FROM usuario;
-- SHOW WARNINGS aclara, por ejemplo, por qué un índice no fue usado
EXPLAIN SELECT * FROM usuario;
SHOW WARNINGS;
La parte crítica es interpretar el resultado. Las columnas devueltas son:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
A continuación, se explica cada una en detalle.
Campo id
Identifica el orden de ejecución de las consultas o subconsultas. Un número de id más alto indica mayor prioridad de ejecución. Se presentan tres escenarios:
- Idénticos: Cuando todos los
idson iguales, las tablas se procesan en el orden en que aparecen en el plan, de arriba hacia abajo. - Diferentes: Si los
idvarían, el que tenga el valor más alto se ejecuta primero. - Mixtos: Se ejecutan primero los
idmás altos, y luego, entre los del mismo nivel, se sigue el orden ascendente de la lista.
Campo select_type
Indica el tipo de consulta. Los valores más comunes son:
SIMPLE: Consulta simple sin subconsultas niUNION.PRIMARY: Consulta principal en una subconsulta.SUBQUERY: Subconsulta dentro deSELECToWHERE.DERIVED: Subconsulta en la cláusulaFROM; MySQL la ejecuta y guarda el resultado en una tabla temporal.UNION: Segundo o posterioresSELECTen unaUNION.DEPENDENT UNION: Similar aUNION, pero depende del resultado de la consulta externa.UNION RESULT: Operación que fusiona los resultados de unaUNION.
Campo table
Muestra la tabla a la que se refiere la fila. Si aparece entre corchetes angulares, como <derivedN>, es una tabla temporal generada internamente, donde N es el id de la consulta que la produce.
Campo partitions
Indica las particiones que coinciden con la consulta. En tablas no particionadas, el valor es NULL.
Campo type
El método de acceso a los datos. Ordenados de mejor a peor rendimiento:
system > const > eq_ref > ref > range > index > ALL
Se recomienda alcanzar al menos range; idealmente ref o superior.
system: Solo una fila en la tabla (caso especial deconst). Es muy raro.const: Búsqueda por clave primaria o índice único que retorna una sola fila.
EXPLAIN SELECT * FROM producto WHERE id = 127;
eq_ref: En joins, cuando se usa una clave primaria o índice único como condición de enlace. Para cada fila de la tabla anterior, solo hay una coincidencia.
EXPLAIN SELECT * FROM inventario, producto WHERE producto.id = inventario.producto_id;
ref: Búsqueda por índice no único que devuelve varias filas que coinciden con un valor.
EXPLAIN SELECT * FROM regla WHERE plan_id = 127;
range: Búsqueda por rango usando un índice (operadores comoBETWEEN,IN,>,<).
EXPLAIN SELECT * FROM regla WHERE plan_id > 127;
index: Recorrido completo del índice (full index scan). Si enExtraapareceUsing index, la consulta se resuelve solo con el índice. Si no, puede incluir lecturas de la tabla.
EXPLAIN SELECT id FROM regla;
index_merge: Combinación de varios índices para resolver la consulta.
EXPLAIN SELECT * FROM regla WHERE plan_id = 5 OR id = 10;
ALL: Escaneo completo de la tabla; debe evitarse.
Campo possible_keys
Lista los índices que MySQL podría usar. No garantiza que realmente los utilice.
Campo key
El índice que MySQL eligió realmente.
Campo key_len
Longitud en bytes del índice utilizado. Cuanto más corto, mejor (siempre que no se pierda precisión). Depende de la estructura de la tabla, no de los datos reales.
Fórmulas aproximadas para calcular key_len:
- Cadenas:
charsize * column_length + 1 (si permite NULL) + 2 (si es varchar)
Dondecharsizees 4 para utf8mb4, 3 para utf8, 2 para gbk, 1 para latin1. - Numéricos:
tinyint: 1,smallint: 2,int: 4,bigint: 8. Se suma 1 si permite NULL. - Fechas:
date: 3,timestamp: 4,datetime: 8. Se suma 1 si permite NULL.
Campo ref
Muestra las columnas o constantes usadas para buscar en el índice indicado en key. Los valores comunes son const (constante) o el nombre de una columna.
Campo rows
Estimación del número de filas que MySQL examinará para ejecutar la consulta.
Campo filtered
Porcentaje estimado de filas que cumplen las condiciones de la consulta. Multiplicando rows * filtered / 100 se obtiene una estimación del número de filas que se pasarán a la siguiente etapa.
Campo Extra
Contiene información adicional sobre la ejecución.
Using filesort: MySQL necesita realizar una ordenación adicional que no puede hacer solo con el índice. Es una operación costosa que conviene optimizar. El espacio de memoria para la ordenación está controlado porsort_buffer_size(por defecto 2 MB). Si es insuficiente, se usan archivos temporales. Para monitorear el número de fusiones:
SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';
Si el valor es alto, considere aumenatr sort_buffer_size.
Using temporary: Se crea una tabla temporal para almacenar resultados intermdeios, a menudo en consultas conGROUP BYu ordenación compleja. Debe evitarse. El límite de tamaño para tablas temporales en memoria es el menor entretmp_table_sizeymax_heap_table_size. Si se supera, se usa disco. Para monitorear:
SHOW GLOBAL STATUS LIKE '%tmp%';
Ejemplo de salida:
+-------------------------+---------+
| Variable_name | Value |
+-------------------------+---------+
| Created_tmp_disk_tables | 4025 |
| Created_tmp_files | 6366 |
| Created_tmp_tables | 2096332 |
+-------------------------+---------+
Created_tmp_disk_tables es el total de tablas temporales creadas en disco.
Using index: La consulta se resuelve completamente con el índice (covering index), sin acceder a la tabla. Si aparece junto aUsing where, el índice se usó tanto para la búsqueda como para la recuperación de datos.Using where: Se aplica un filtroWHEREdespués de leer los datos. Puede indicar que no se usan solo índices.Using join buffer: En unJOIN, cuando no se usa un índice en la condición de enlace, MySQL necesita un buffer para almacenar filas intermedias. Se recomienda agregar un índice adecuado.Impossible WHERE: La condiciónWHEREsiempre es falsa; no se devolverán filas.
Para profundizar, consulte la documentación oficial de MySQL sobre planes de ejecución.