Optimizar el rendimiento de MySQL 8: innodb_buffer_pool_size, EXPLAIN y slow query log

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:

  1. Activar el slow query log y localizar las consultas lentas.
  2. Analizar cada una con EXPLAIN o EXPLAIN ANALYZE.
  3. Añadir los índices que faltan o reescribir la consulta.
  4. 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

sort_buffer_size

sort_buffer

2-4 MB

ORDER BY y GROUP BY; por hilo

read_buffer_size

record_buffer

128-256 KB

Escaneos secuenciales de MyISAM; limitado impacto en InnoDB

read_rnd_buffer_size

record_rnd_buffer

256 KB - 1 MB

Lecturas tras un sort; por hilo

join_buffer_size

—

256 KB - 1 MB

JOINs sin índice; mejor añadir el índice

table_open_cache

table_cache

400-2000

Descriptores de tabla abiertos en caché

max_connections

—

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: ALL es escaneo completo (malo); ref, range o eq_ref indican uso de índice (bueno).
  • key: qué índice usa. NULL significa 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:

  1. 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.
  2. No experimentar en producción. Usa un entorno de pruebas con una copia real de los datos. Un cambio en innodb_buffer_pool_size requiere reiniciar MySQL (salvo que uses innodb_buffer_pool_chunk_size y el modo online de MySQL 5.7+).
  3. Usar datos reales. Las pruebas con datos ficticios no reflejan el comportamiento real del optimizador.
  4. Medir antes y después. Sin una línea base, no puedes saber si tus cambios han mejorado algo.
  5. 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

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP