...

Analizar el registro de consultas lentas de MySQL en el alojamiento: Consejos de optimización

Registro de consultas lentas de MySQL me muestra en el alojamiento qué consultas consumen tiempo, con qué frecuencia se producen y por qué ralentizan las cosas. Te muestro pasos concretos sobre cómo activar el registro, analizarlo y reconstruir las consultas para que las páginas se carguen más rápido y los recursos del servidor funcionen de forma más eficiente.

Puntos centrales

  • Activación y fijar los valores umbral con sensatez
  • Evaluación con pt-query-digest y mysqldumpslow
  • Métricas interpretar: Tiempo_consulta, Tiempo_bloqueo, Filas_examinadas
  • Sintonización mediante índices, EXPLAIN y reescrituras
  • Automatización y supervisión en el alojamiento

¿Qué hace el registro de consultas lentas en el alojamiento?

Alojamiento significa recursos compartidos, por lo que cada milisegundo por consulta cuenta. Utilizo el registro para encontrar consultas que se ejecutan durante más tiempo que un límite definido y veo cifras clave como Query_time, Lock_time, Rows_sent y Rows_examined para cada consulta. Estas cifras me muestran si hay detrás un índice ausente, una unión desfavorable o un escaneo completo de la tabla. Especialmente en servidores con varios sitios, una sola consulta incorrecta puede sobrecargar mucho la CPU y la E/S. A continuación, doy prioridad a las consultas con el tiempo total más elevado, porque es ahí donde reside el mayor efecto multiplicador sobre el tiempo de carga y la carga del servidor.

Valores umbral de activación y sensibles

InicioPuedo en tiempo de ejecución o permanentemente a través de my.cnf, dependiendo del acceso en el hosting. Para pruebas rápidas, activo el log temporalmente y establezco long_query_time a un valor que se ajuste al tráfico y al hardware. A menudo voy a 0.1 segundos para sitios muy usados, pero controlo el tamaño del log para que la E/S no crezca innecesariamente. Si los accesos directos a archivos están limitados, utilizo las opciones de esquema de rendimiento del intérprete de comandos de MySQL para generar informes. Tras el ajuste fino, escribo los ajustes finales en el archivo de configuración y reinicio el servicio.

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow-query.log';
SET GLOBAL log_queries_not_using_indexes = 'ON';

SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

Permanente Establezco opciones como log_throttle_queries_not_using_indexes y log_slow_admin_statements para que el registro siga siendo útil y no explote. Yo documento cada valor, por ejemplo por qué long_query_time es 0,5 o 0,1 segundos. Esto me permite refinarlo más tarde. En los entornos compartidos, a menudo discuto la activación con el proveedor o utilizo su panel. Vinculo cada activación con una fecha de inicio para poder comparar limpiamente los efectos en la supervisión y las métricas.

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 1
log_queries_not_using_indexes = 1
log_throttle_queries_not_using_indexes = 10
log_slow_admin_statements = 1

Evaluar eficazmente el registro lento

Datos brutos son ruidosas, así que las resumo con pt-query-digest y las ordeno por tiempo total durante un periodo de tiempo significativo. Así es como reconozco los patrones, las consultas muy variables y las familias de consultas que sólo varían en función de los parámetros. Compruebo la distribución, no sólo la media, porque los valores atípicos causan verdaderos problemas al usuario. Para una visión rápida, mysqldumpslow me ayuda a ver los diez grupos más lentos. Obtengo más profundidad utilizando ventanas de tiempo, filtros de base de datos y una exportación a un análisis de texto.

pt-query-digest /var/log/mysql/slow-query.log
pt-query-digest --since '24h' /var/log/mysql/slow-query.log
mysqldumpslow -s t -t 10 /var/log/mysql/slow-query.log

Útil es también un vistazo a otros registros cuando entran en juego funciones de la aplicación o de PHP. Para ello, utilizo flujos de trabajo de registro existentes y agrupo los resultados. Esta guía a menudo me proporciona una introducción: Analizar registros. Sincronizo las marcas de tiempo para poder comparar los picos de tráfico con los picos de consulta. Esto me permite ver si los errores de caché, los trabajos cron o los trabajos de importación están utilizando la base de datos al mismo tiempo.

Interpretar correctamente las métricas

Hora_consulta me muestra el tiempo de ejecución puro; priorizo primero las consultas de más de un segundo. Lock_time indica los tiempos de espera debidos a los bloqueos, que a menudo resultan de transacciones innecesariamente largas o de grandes lotes. El ratio de Rows_examined to Rows_sent me indica si las consultas están viendo demasiadas filas y faltan índices. Si el registro contiene muchas entradas de „Sin uso de índice“, establezco el estrangulamiento y examino más de cerca las tablas afectadas. Sigue siendo importante abordar siempre la causa y no el síntoma: Un índice en la columna correcta es mejor que cualquier actualización de hardware.

Métricas Lo que veo Medida
Tiempo_consulta alto Larga duración por versión Comprobar EXPLAIN, reescribir consulta, añadir índice
Tiempo_bloqueo alto Tiempo de espera de las cerraduras Acortar las transacciones, reducir el tamaño de los lotes, aislamiento adecuado
Filas_examinadas ≫ Filas_enviadas Escaneado excesivo, devolución insuficiente Columnas de filtro de índice, crear sargabilidad
No se utiliza ningún índice Escaneado completo de la tabla Crear índice, evitar expresión en WHERE

Valores límite Lo ajusto después de la primera semana para no perderme en el ruido. Reduzco long_query_time por etapas hasta que tengo suficientes aciertos para mejoras sistemáticas. Documento cada ajuste con la fecha y el motivo. Así mantengo la evaluación centrada. Los aciertos valiosos me ahorran trabajo duplicado más adelante.

Práctica: Ajuste de consultas paso a paso

EXPLICAR es mi comienzo antes de cambiar el código. Busco „type: ALL“, „rows“ con números grandes y „Using filesort“ o „Using temporary“. Las funciones sobre columnas en WHERE o JOIN suelen impedir el uso de índices. En su lugar, formulo condiciones sargables y luego compruebo el nuevo plan. Cada paso debe realizar la reducción de filas de forma temprana y decidida.

EXPLAIN SELECT * FROM pedidos WHERE AÑO(fecha_creación) = 2026;

-- Mejor:
CREATE INDEX idx_orders_created ON orders(created_at);
SELECT * FROM pedidos
WHERE fecha_creada >= '2026-01-01' AND fecha_creada < '2027-01-01';

Se une a Optimizo comprobando el orden de unión y los índices coincidentes en las claves de unión. Compruebo si un índice compuesto cubre WHERE + ORDER BY para evitar filesort. Establezco LIMIT cuando sólo es necesaria una vista previa. Guardo los resultados en caché a nivel de aplicación para consultas repetidas e idénticas con una baja tasa de cambio. Puedes encontrar una introducción más profunda a los índices y bloqueos aquí: Índices y bloqueo.

Estrategias de indexación para CMS y tiendas

WordPress, Los sistemas WooCommerce o de tiendas crean patrones típicos: mucha lectura, escritura selectiva, a menudo con tablas meta o de productos. Analizo las rutas más habituales -página de inicio, categoría, búsqueda, pago- y coloco índices específicamente en las columnas de filtrado, ordenación y unión. Los índices de cobertura (por ejemplo, (status, created_at, id)) ahorran mucho recurso a la tabla. Para buscar prefijos, utilizo formas de índice adecuadas o texto completo en lugar de LIKE ‚%wort%‘. Mido cada cambio de índice antes y después de la ejecución en vivo con los mismos perfiles de carga.

Crecimiento Utilizo la cardinalidad y los histogramas para comprobar los conjuntos de datos y no indexar valores poco frecuentes. Mantengo bajo el número de índices para controlar la carga de escritura y los requisitos de memoria. Los índices compuestos consolidados sustituyen a varios índices individuales. Regulo las tareas tipo autovacuum en MySQL analizándolas regularmente y reconstruyéndolas sólo cuando es necesario. Esto mantiene la fiabilidad del optimizador.

Configuración del servidor, caché y memoria

InnoDB Determino el tamaño del buffer pool en función de los registros de datos activos y del tamaño de los índices, no según valores globales. Lo aumento hasta que el tamaño del conjunto de trabajo se encuentra en gran parte en la memoria y la tasa de pérdidas de página disminuye. Establezco tmp_table_size y max_heap_table_size para que menos tablas temporales acaben en el disco. Para la seguridad y latencia de escritura, balanceo innodb_flush_log_at_trx_commit apropiadamente para la aplicación. A nivel de aplicación, almaceno en caché los resultados frecuentes y utilizo la caché HTTP para que la base de datos reciba menos peticiones.

Hardware y los efectos de red se incluyen en el diagnóstico: Las consultas reconocen inmediatamente la lentitud de las E/S de almacenamiento o la sobrecarga de la CPU. Por lo tanto, mido la IO-espera en paralelo con las métricas de la base de datos. Si necesita más reservas, planifique el escalado vertical u horizontal con un objetivo medible. Esta guía le ofrece una visión general compacta de los cuellos de botella, el ajuste y los recursos: Hardware y caché. Así me aseguro de no estar girando a ciegas el mando equivocado.

Concurrencia y bloqueo en el alojamiento

Hora_bloqueo crece cuando las transacciones largas tocan muchas líneas o cuando los trabajos de limpieza se ejecutan en horas punta. Acorto las operaciones de escritura, divido las grandes actualizaciones en lotes más pequeños y reduzco así el tiempo de mantenimiento de los bloqueos. Unos niveles de aislamiento adecuados reducen los conflictos sin poner en peligro la coherencia de los datos. Alivio los puntos calientes con índices secundarios y condiciones WHERE adecuadas para que se vean afectadas menos filas. Programo los trabajos en segundo plano en franjas horarias de poco tráfico para dar prioridad a las acciones de los usuarios.

Bloqueos Los analizo utilizando patrones recurrentes: mismas tablas, secuencia cambiante, líneas idénticas. Normalizo la secuencia de acceso en el código y los procedimientos almacenados. La lógica de reintentos con jitter resuelve las colisiones temporales. En la medida de lo posible, aíslo las operaciones más costosas en colas de trabajo. Esto reduce notablemente la varianza y aumenta el rendimiento percibido.

Alarmas y flujos de trabajo automatizados

Rutina late accionismo: analizo el registro diaria o semanalmente, en función del tráfico y la frecuencia de publicación. Un pequeño script cuenta los nuevos accesos en los últimos minutos y me envía un correo electrónico si aumenta el valor del umbral. También genero informes pt-query-digest con regularidad y siempre vigilo los 10 primeros. Sigo más de cerca los días de lanzamiento. Esto me permite reconocer las regresiones antes de que los usuarios las noten.

#!/bin/bash
LOG_FILE="/var/log/mysql/slow-query.log"
UMBRAL=100
RECENT_COUNT=$(awk -v cutoff="$(date -d '5 minutes ago' '+%Y-%m-%dT%H:%M')" '/^# Time:/ { if ($3 >= cutoff) count++ } END { print count+0 }' "$LOG_FILE")
if [ "$RECENT_COUNT" -gt "$THRESHOLD" ]; then
    echo "ALERTA: $RECENT_COUNT consultas lentas" | mail -s "Alerta MySQL" [email protected]
fi

Transparencia Creo responsabilidades claras: Quién reacciona ante los picos, quién ajusta los índices, quién prueba los lanzamientos. Resumo los resultados en breves registros de cambios. Así, todos los miembros del equipo entienden por qué se ha hecho un cambio y qué efecto ha tenido. Un proceso estructurado ahorra tiempo y evita falsas alarmas.

Imágenes de errores y correcciones rápidas

Completo Los escaneos de tablas provocan una carga desproporcionadamente alta. Primero compruebo si falta un índice adecuado en la columna del filtro o si una expresión está bloqueando el índice. Elimino el tiempo de bloqueo acortando las transacciones e igualando las operaciones que compiten entre sí. Elimino los registros desbordados con log_throttle_queries_not_using_indexes y un long_query_time realista. Mido inmediatamente cada corrección con respecto a las cifras originales para que los éxitos sigan siendo visibles.

Almacenamiento-Reconozco los cuellos de botella aumentando la IO-espera y la alta latencia de disco durante los picos de consulta. A continuación, reduzco las operaciones de escritura innecesarias, por ejemplo actualizando con menos frecuencia los campos inmutables. Cuando las tablas crecen, planifico estrategias de archivo o partición para que los datos calientes permanezcan en memoria. Para las sentencias de administración en horas punta, activo log_slow_admin_statements para identificar los generadores de costes silenciosos. En este caso, las pequeñas correcciones específicas dan sus frutos más rápidamente que las grandes reorganizaciones.

Funciones especiales en entornos gestionados y en la nube

alojamiento gestionado o los servicios en la nube suelen limitar el acceso a los archivos. En tales casos, establezco log_output en TABLE y evalúo el registro lento directamente desde la base de datos. En MySQL 8.0, también utilizo SET PERSIST para establecer permanentemente los parámetros sin acceso directo a my.cnf. En grupos de parámetros de nube (por ejemplo, para servicios gestionados), introduzco las mismas variables y programo una ventana de mantenimiento para el reinicio.

-- Si está permitido: Configuración persistente sin reinicio
SET PERSIST slow_query_log = ON;
SET PERSIST long_query_time = 0.5;
SET PERSIST log_output = 'TABLE'; -- Alternativa a FILE para acceso restringido a ficheros

-- Evaluación con log_output=TABLE
SELECT start_time, user_host, query_time, lock_time, rows_sent, rows_examined, sql_text
FROM mysql.slow_log
ORDER BY tiempo_consulta DESC
LIMIT 50;

NotaCon mucho tráfico, log_output=FILE puede ser más eficaz, ya que el registro en tablas genera una sobrecarga adicional. En entornos restrictivos, sin embargo, TABLE es a menudo el único camino a seguir. A continuación, establezco límites más estrictos (por ejemplo, min_examined_row_limit) para mantener el volumen controlable.

Rotación, almacenamiento y protección de datos

Rotación evita que los registros llenen el disco. Hago una rotación diaria o por tamaño, comprimo los archivos antiguos y mantengo una política de retención clara (por ejemplo, 14 días). Tras la rotación, desencadeno un vaciado del registro para que MySQL escriba limpiamente en el nuevo archivo. Esto mantiene estables el análisis y el funcionamiento.

# /etc/logrotate.d/mysql-slow
/var/log/mysql/slow-query.log {
  diario
  rotar 14
  tamaño 100M
  comprimir
  missingok
  notifempty
  crear 640 mysql adm
  postrotate
    test -x /usr/bin/mysqladmin || exit 0
    /usr/bin/mysqladmin flush-logs
  endscript
}

Protección de datos es obligatorio: los registros lentos pueden contener valores de parámetros. Limito estrictamente el acceso (permisos de archivos, grupos) y compruebo si se están registrando datos sensibles. Si es necesario, trabajo con la vinculación de parámetros en la aplicación para que no aparezca texto plano personalizado en el registro. Para compartir en equipo, prefiero compartir informes agregados en lugar de registros sin procesar.

Utilizar el esquema de rendimiento y el esquema de sistema

Programa de resultados proporciona métricas incluso sin un registro lento activado. Activo los consumidores relevantes para las sentencias y luego analizo las vistas del sistema. Ventaja: puedo ver los mejores resúmenes y la distribución de la latencia casi en tiempo real, agrupados en consultas similares.

-- Activar consumidor para historial de sentencias (en la medida de lo posible en tiempo de ejecución)
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME IN ('events_statements_history', 'events_statements_history_long');

-- Vista rápida de los grupos de consulta caros
SELECT nombre_esquema, digest_texto, count_star,
       ROUND(sum_timer_wait/1e12, 3) AS total_s,
       ROUND(avg_timer_wait/1e9, 3) AS avg_ms,
       ROUND(max_timer_wait/1e9, 3) AS pmax_ms
FROM sys.statement_analysis
ORDER BY sum_timer_wait DESC
LIMIT 10;

Combinación del registro de lentitud (valores atípicos lentos) y el esquema de rendimiento (anchura, frecuencia) me muestra tanto casos individuales como generadores de costes sistemáticos. Comparo ambas vistas con los patrones de tráfico para crear tareas prioritarias.

EXPLAIN ANALYZE y Optimiser Trace

EXPLAIN ANALYZE (a partir de MySQL 8.0.18) complementa las estimaciones con tiempos medidos. Comparo las estimaciones de filas con los valores reales y descubro errores de apreciación del optimizador. En el caso de planes contradictorios, analizo la traza del optimizador para ver por qué no se seleccionó un índice.

-- Planificar con valores medidos
EXPLICAR ANALIZAR
SELECT o.id, o.created_at
FROM pedidos o
JOIN clientes c ON c.id = o.customer_id
WHERE c.country = 'DE' AND o.status = 'paid'
ORDER BY o.created_at DESC
LIMIT 50;

-- Trazar las decisiones del optimizador
SET optimizer_trace="enabled=on";
SELECT ...; -- consulta a analizar
SELECT TRACE FROM esquema_informacion.OPTIMIZER_TRACE\G
SET optimiser_trace="enabled=off";

ResultadoSi las estimaciones son erróneas, actualizo las estadísticas (ANALIZAR TABLA), añado histogramas o reformulo los índices/consultas para que la selectividad surta efecto antes.

Reescribir patrones que casi siempre funcionan

O a UNION ALLMúltiples condiciones OR en diferentes columnas a menudo impiden el uso de índices. Yo las separo en dos consultas selectivas y fusiono los resultados si se pueden excluir los duplicados.

-- Antes:
SELECT * FROM t WHERE a = ? OR b = ?

-- Mejor:
(SELECT * FROM t WHERE a = ?)
UNION ALL
(SELECT * FROM t WHERE b = ? AND a  ?);

PaginaciónOFFSET/LIMIT se encarece al aumentar OFFSET. Paso a la paginación por conjuntos de teclas y utilizo una tecla de ordenación adecuada (idealmente indexada y monótona).

-- Caro:
SELECT id, title FROM posts ORDER BY created_at DESC LIMIT 50 OFFSET 5000;

-- Mejor (keyset):
SELECT id, title
FROM posts
WHERE fecha_creada < :cursor
ORDER BY fecha_creada DESC
LIMIT 50;

Índices compuestosOrdenación de recuentos. Ordeno las columnas del índice según la selectividad y el patrón de consulta (primero filtro WHERE, luego ordeno las columnas). El objetivo es un índice de cobertura que evite filesort y búsquedas en tablas.

Índices funcionales y generados en MySQL 8

Expresiones en WHERE/JOIN a menudo bloquean los índices. En MySQL 8.0, índice específicamente expresiones o trabajo con columnas generadas para crear sargability. Esto es particularmente útil para CASTs para meta valores numéricos o campos JSON.

-- Ejemplo: ordenación numérica en campo de texto
ALTER TABLE product ADD COLUMN precio_num DECIMAL(10,2)
  GENERADO SIEMPRE COMO (CAST(precio COMO DECIMAL(10,2))) ALMACENADO;
CREATE INDEX idx_product_price_num ON product(price_num);

-- Consulta sin CAST y con índice
SELECT * FROM producto
WHERE precio_num ENTRE 10 Y 50
ORDER BY precio_num;

PrácticaPruebo si el nuevo índice funciona realmente (EXPLAIN) y mido el efecto en el registro lento. Las columnas generadas también ayudan a filtrar prefijos o variantes normalizadas (LOWER(email)) de forma eficiente.

Un enfoque más específico de los modelos CMS/tienda

Meta tablas (por ejemplo, wp_postmeta) se benefician de índices combinados sobre (post_id, meta_key) o (meta_key, meta_value). Para los filtros frecuentes sobre meta_value_numeric, utilizo columnas generadas como arriba en lugar de CASTing en cada consulta. Acelero las páginas de búsqueda descartando redundancias (desnormalización ligera) y facilitando el acceso de lectura al índice.

-- Típico de WordPress: acceso rápido a los metadatos de una entrada
CREATE INDEX idx_postmeta_postid_metakey ON wp_postmeta(post_id, meta_key);
CREATE INDEX idx_postmeta_metakey_metavalue ON wp_postmeta(meta_key, meta_value(100));

Pedido-Optimizo los recorridos para que los tiempos de bloqueo sean mínimos: transacciones cortas, sólo las filas necesarias e índices exactos para las condiciones WHERE utilizadas. Para los informes, planifico la agregación asíncrona (tablas intermedias) para no ralentizar los flujos de usuarios.

Límites del registro lento y métricas suplementarias

Muchas consultas pequeñas y rápidas no se aprecian en el registro lento, pero se suman a la carga. Por tanto, también realizo un seguimiento del rendimiento (consultas/seg), los percentiles 95/99 y la proporción de consultas sin índice. En las herramientas Performance Schema o APM, reconozco los patrones N+1, que luego resuelvo específicamente mediante uniones, procesos de carga por lotes o almacenamiento en caché.

Muestreo es útil cuando los registros son demasiado grandes. Aumento ligeramente long_query_time o establezco min_examined_row_limit para incluir sólo las consultas relevantes. Importante: anota siempre los cambios para que las series temporales sigan siendo comparables.

Método de trabajo: De las conclusiones a la mejora sostenible

Línea de base Primero: guardo un informe previo (ventana de tiempo, tráfico, configuración). A continuación, optimizo una familia de consultas tras otra y comparo ventanas de tiempo idénticas. Cada corrección se documenta en el repositorio (¿Qué? ¿Por qué? ¿Valor medido antes/después?). De este modo, los éxitos siguen siendo trazables y a prueba de regresiones.

# Procedimiento aproximado (ejemplo)
1) pt-query-digest --since '7d' slow-query.log > baseline.txt
2) Seleccionar los 3 mejores compendios de consultas (por tiempo total)
3) EXPLAIN/EXPLAIN ANALYZE, elaborar propuestas de índice y reescritura
4) Generar datos de prueba, simular el perfil de carga
5) Despliegue con supervisión (límites más estrictos durante 48 horas)
6) Informe comparativo: pt-query-digest --since '48h' > after.txt
7) Documentación de los resultados, planificación de la siguiente fase

Plan de estabilidad Superviso el optimizador a lo largo del tiempo: si cambian los planes (nuevas versiones, estadísticas modificadas), compruebo los histogramas, ANALYZE TABLE y el panorama de índices. Solo establezco sugerencias de forma selectiva y documentada para no bloquear el optimizador de forma permanente.

Resumen en pasos claros

Inicio significa: activar el registro, establecer valores límite razonables, recopilar la primera semana de datos. Luego resumo con pt-query-digest, priorizo según el tiempo total y la varianza y selecciono los mejores controladores. Optimizo las consultas con EXPLAIN, condiciones sargables e índices adecuados y controlo el bloqueo con transacciones más cortas. En el servidor, configuro adecuadamente los búferes, las tablas temporales y las estrategias de descarga. Por último, automatizo las alarmas y repito el ciclo con regularidad: así mantengo la rapidez de la base de datos, incluso cuando aumentan el tráfico y el volumen de datos.

Artículos de actualidad