Cuando MySQL empieza a tirar el rendimiento por los suelos lo primero que hacemos es buscar en Google «mysql más rápido» y nos encontramos con artículos que hablan de key_buffer y de tablas MyISAM. Eso era 2003. En 2026 el 99% de las instalaciones usan InnoDB, y los parámetros que realmente importan son otros. Este artículo, que nació como una traducción del equipo de MySQL Hispano y fue actualizado por David Carrero en 2026, recorre los ajustes que de verdad mueven la aguja en MySQL 8.
El punto de partida: antes de tocar my.cnf
Hay una regla que el artículo original ya enunciaba y que sigue siendo válida: ningún ajuste del servidor arregla consultas mal escritas o tablas sin índices. Si una consulta hace un escaneo completo de tabla de diez millones de filas, aumentar innodb_buffer_pool_size no la va a salvar. La secuencia correcta siempre es:
- Activar el slow query log y localizar las consultas lentas.
- Analizar cada una con
EXPLAINoEXPLAIN ANALYZE. - Añadir los índices que faltan o reescribir la consulta.
- Solo después, si el servidor sigue saturado, ajustar variables de configuración.
Con ese orden en la cabeza, vamos a los parámetros.
innodb_buffer_pool_size: el ajuste más importante
En el artículo original, el protagonista era key_buffer_size, que cachea los índices de tablas MyISAM. Con InnoDB (el motor por defecto desde MySQL 5.5) ese parámetro es irrelevante. El equivalente moderno es innodb_buffer_pool_size, que almacena en memoria tanto los índices como los datos de InnoDB.
La recomendación general: en un servidor dedicado exclusivamente a MySQL, asignar entre el 50% y el 70% de la RAM. Con 16 GB de memoria:
innodb_buffer_pool_size = 10G
Puedes comprobarlo sin reiniciar:
SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests'; SHOW STATUS LIKE 'Innodb_buffer_pool_reads';
La ratio que debes vigilar es análoga a la del key_buffer de antes:
-- Hit rate del buffer pool (cuanto más cerca de 100, mejor)
SELECT (1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)) * 100 AS hit_rate
FROM (
SELECT
VARIABLE_VALUE AS Innodb_buffer_pool_reads
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads'
) r,
(
SELECT VARIABLE_VALUE AS Innodb_buffer_pool_read_requests
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'
) rr;
Por debajo del 95% empieza a valer la pena aumentar el buffer pool si hay RAM disponible.
innodb_log_file_size y el redo log
El redo log (fichero de transacciones) determina cuántos datos puede escribir MySQL antes de forzar un flush al disco. Un redo log demasiado pequeño provoca flushes frecuentes y penaliza escrituras.
En MySQL 8.0 hasta la versión 8.0.29, el parámetro era innodb_log_file_size. A partir de 8.0.30 se reemplazó por innodb_redo_log_capacity:
-- MySQL 8.0.30+ innodb_redo_log_capacity = 2G -- MySQL 8.0.29 y anteriores (también funciona como fallback) innodb_log_file_size = 512M innodb_log_files_in_group = 2
Para cargas de trabajo con muchas escrituras, un redo log de 1-2 GB es un punto de partida razonable.
Parámetros de conexión y sesión
A diferencia del buffer pool (global), los parámetros de sesión se reservan por cada hilo activo. Si subes sort_buffer_size a 64 MB y tienes 200 conexiones concurrentes, estás comprometiendo 12 GB solo en buffers de ordenación. La cautela es obligatoria.
Parámetro | Equivalente antiguo | Valor orientativo | Notas |
|
| 2-4 MB | ORDER BY y GROUP BY; por hilo |
|
| 128-256 KB | Escaneos secuenciales de MyISAM; limitado impacto en InnoDB |
|
| 256 KB - 1 MB | Lecturas tras un sort; por hilo |
| | 256 KB - 1 MB | JOINs sin índice; mejor añadir el índice |
|
| 400-2000 | Descriptores de tabla abiertos en caché |
| | 200-500 | Aumentar con cuidado; cada conexión usa memoria |
Nota: la caché de consultas (query_cache_size) que era estrella en MySQL 4.0 fue eliminada definitivamente en MySQL 8.0. No intentes configurarla.
El slow query log: tu mejor aliado
Antes de tocar ningún parámetro de memoria, activa el slow query log. Registra automáticamente las consultas que superan un umbral de tiempo:
-- En my.cnf / my.ini slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 -- segundos; bajar a 0.5 en producción activa log_queries_not_using_indexes = ON
O en caliente, sin reiniciar:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; SET GLOBAL log_queries_not_using_indexes = ON;
Una vez que tienes datos, la herramienta mysqldumpslow (incluida con MySQL) agrupa las consultas lentas por patrón:
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
Esto te da las 10 consultas que más tiempo acumulado consumen: exactamente por dónde tienes que empezar.
EXPLAIN y EXPLAIN ANALYZE
Una vez identificada una consulta problemática, EXPLAIN muestra el plan de ejecución que MySQL ha elegido:
EXPLAIN SELECT p.nombre, SUM(l.cantidad * l.precio_unitario) AS total FROM pedidos pe JOIN lineas_pedido l ON l.id_pedido = pe.id_pedido JOIN productos p ON p.id_producto = l.id_producto WHERE pe.fecha_pedido >= '2025-01-01' GROUP BY p.id_producto;
Las columnas que más importan en la salida:
- type:
ALLes escaneo completo (malo);ref,rangeoeq_refindican uso de índice (bueno). - key: qué índice usa.
NULLsignifica que no usa ninguno. - rows: filas estimadas que examinará. Multiplícalo por todas las tablas del JOIN para hacerte una idea del coste.
- Extra: «Using filesort» o «Using temporary» son señales de alerta.
MySQL 8.0 añadió EXPLAIN ANALYZE, que ejecuta la consulta y muestra tiempos reales frente a estimados:
EXPLAIN ANALYZE SELECT ...;
Es especialmente útil cuando el estimador de costes se equivoca: verás dónde divergen las filas estimadas de las reales.
performance_schema y sys schema
MySQL 5.5 introdujo performance_schema y MySQL 5.7 añadió el sys schema, que es básicamente una capa de vistas legibles sobre performance_schema. En MySQL 8 vienen activados por defecto.
Las vistas más útiles del día a día:
-- Las 10 consultas que más tiempo total acumulan
SELECT query, total_latency, exec_count, avg_latency
FROM sys.statement_analysis
ORDER BY total_latency DESC LIMIT 10;
-- Tablas con más escaneos completos (candidates para índices)
SELECT table_schema, table_name, rows_full_scanned
FROM sys.schema_tables_with_full_table_scans
ORDER BY rows_full_scanned DESC LIMIT 10;
-- Índices que nadie usa (candidatos a eliminar)
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema NOT IN ('performance_schema', 'sys');
-- Conexiones activas y qué hacen
SELECT * FROM sys.processlist WHERE conn_id != CONNECTION_ID();
SHOW STATUS: los contadores de MySQL
El artículo original explicaba cómo calcular el hit rate del key_buffer. El mismo principio aplica hoy:
-- Estado general de InnoDB SHOW ENGINE INNODB STATUSG -- Contadores relevantes SHOW STATUS LIKE 'Threads_%'; SHOW STATUS LIKE 'Questions'; SHOW STATUS LIKE 'Slow_queries'; SHOW STATUS LIKE 'Select_full_join'; -- JOINs sin índice SHOW STATUS LIKE 'Select_scan'; -- escaneos completos SHOW STATUS LIKE 'Created_tmp_disk_%'; -- tablas temporales en disco
Created_tmp_disk_tables alto indica que MySQL desborda la memoria para tablas temporales y las escribe a disco. El parámetro tmp_table_size y max_heap_table_size controlan el límite en memoria.
Reglas de ajuste sistemático
El artículo original listaba cinco reglas de buenas prácticas que siguen siendo completamente válidas:
- Cambiar un parámetro cada vez. Si cambias cinco cosas a la vez y el rendimiento mejora (o empeora), no sabrás qué lo ha causado.
- No experimentar en producción. Usa un entorno de pruebas con una copia real de los datos. Un cambio en
innodb_buffer_pool_sizerequiere reiniciar MySQL (salvo que usesinnodb_buffer_pool_chunk_sizey el modo online de MySQL 5.7+). - Usar datos reales. Las pruebas con datos ficticios no reflejan el comportamiento real del optimizador.
- Medir antes y después. Sin una línea base, no puedes saber si tus cambios han mejorado algo.
- Documentar. Deja registro en el historial de configuración de qué cambiaste, cuándo y por qué.
Un my.cnf mínimo para 2026
Este bloque es un punto de partida razonable para un servidor dedicado con 16 GB de RAM y carga mixta de lectura/escritura. Ajusta según tu caso:
[mysqld] # Motor y charset default_storage_engine = InnoDB character_set_server = utf8mb4 collation_server = utf8mb4_unicode_ci # InnoDB - el ajuste más importante innodb_buffer_pool_size = 10G innodb_buffer_pool_instances = 4 # 1 por cada GB aprox, max 64 innodb_redo_log_capacity = 2G # MySQL 8.0.30+ innodb_flush_log_at_trx_commit = 1 # 1 = ACID estricto; 2 = mejor rendimiento, menor durabilidad innodb_flush_method = O_DIRECT # Conexiones max_connections = 300 table_open_cache = 1000 thread_cache_size = 50 # Slow query log slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = ON # Buffers de sesión (conservadores para muchas conexiones) sort_buffer_size = 2M read_rnd_buffer_size = 512K join_buffer_size = 256K
El papel de MyISAM hoy
El artículo original asumía MyISAM porque InnoDB era opcional en 2003. En MySQL 8.0, InnoDB es el único motor de propósito general que Mysql sigue desarrollando activamente. MyISAM sobrevive para tablas internas del sistema pero no debería usarse en aplicaciones nuevas: no soporta transacciones, no tiene recuperación automática ante fallos y su bloqueo es a nivel de tabla (no de fila).
Si todavía tienes tablas MyISAM heredadas, la migración es directa:
ALTER TABLE mi_tabla_myisam ENGINE = InnoDB;
Imagen: Pexels / panumas nikhomkhai
