En producción, MySQL rara vez falla con dramatismo. Falla por aburrimiento: la latencia p95 se duplica silenciosamente, la CPU parece “bien” y el equipo de producto jura “no desplegamos nada”. Mientras tanto, la base de datos arde—con educación.
La diferencia entre un incidente de dos horas y una solución en diez minutos casi nunca es pura habilidad técnica. Es si tu monitorización te dice qué consulta es la villana, por qué está lenta (CPU, E/S, bloqueos o replicación) y qué cambió.
Qué intentas realmente hacer (y por qué adivinar sale caro)
“Encontrar consultas críticas” suena como un objetivo único, pero en operaciones son tres trabajos separados:
- Detección: identificar el puñado de sentencias que dominan la latencia, CPU, E/S o tiempo de bloqueo.
- Atribución: conectar esas sentencias a una acción de la aplicación, despliegue, feature flag, tarea programada, notebook de analista o un ORM mal configurado.
- Accionabilidad: elegir la corrección adecuada—índice, reescritura de consulta, estabilidad del plan, límites de concurrencia, buffering o cambios de esquema—sin romper todo lo demás.
La trampa de “adivinar” ocurre cuando tu monitorización sólo muestra métricas del host (CPU, disco, red). Esas son necesarias, no suficientes. Las métricas del host te dicen que la base de datos está descontenta. No te dicen quién la hizo descontenta.
Quieres tres capas de visibilidad:
- Capa SQL: huellas normalizadas de consultas, distribución de latencias, filas examinadas vs retornadas, tablas temporales, fusiones de ordenación, errores.
- Capa de esperas: tiempo gastado en bloqueos, E/S, bloqueos de metadatos, buffer pool, redo log y mutex internos.
- Capa de sistema: saturación de CPU, cola de ejecución, latencia de E/S, tiempo de fsync, presión del caché de páginas, reintentos de red.
MySQL puede hacer esto. Percona Server también puede. La diferencia práctica es cuánto puedes hacer antes de un incidente, cuánto cuesta la instrumentación y qué tan fácil es coser pruebas en una línea temporal.
MySQL vs Percona Server: qué cambia para la monitorización
Son primos, no desconocidos
Percona Server for MySQL es una compilación downstream que sigue de cerca a MySQL upstream, con instrumentación adicional y características operativas. En flotas modernas verás:
- Oracle MySQL (community o enterprise): funcionalidades base, Performance Schema, esquema sys, slow log, EXPLAIN, optimizer trace, etc.
- Percona Server: compatible con upstream, más mejoras operativas y controles adicionales (algunos ya no son tan “únicos” como antes, pero siguen importando en producción).
- Percona Monitoring and Management (PMM): no es un fork del servidor, sino una pila de monitorización que hace que el análisis de consultas sea usable para humanos.
Diferencias de monitorización que importan en la práctica
La gran pregunta no es “¿cuál es más rápido?” Es “¿cuál hace que la causa raíz sea obvia bajo estrés?” Así lo describo:
- Experiencia de análisis de consultas: Con MySQL puro, puedes llegar con slow logs + Performance Schema + paneles personalizados. Con Percona Server más PMM, normalmente llegas a las “10 peores huellas de consulta” más rápido y con menos bricolaje.
- Control del coste de instrumentación: Ambos pueden configurarse para evitar sobrecarga intensa, pero el ecosistema de Percona suele convertir “baja sobrecarga, siempre activo” en una práctica cultural más que en una diapositiva aspiracional.
- Métricas y controles extra: Percona Server históricamente incluye variables de estado y características extra en torno a instrumentación y diagnóstico. Incluso cuando upstream se pone al día, Percona tiende a mantener un sesgo operativo: exponer los bordes afilados, no ocultarlos.
Guía opinada: si ejecutas MySQL a escala modesta y ya tienes disciplina de observabilidad, MySQL upstream está bien. Si ejecutas MySQL a escala “por qué esto siempre es urgente” y quieres análisis de consultas que tu on-call pueda usar a las 3 a.m., Percona Server + PMM es difícil de superar.
Una verdad seca: la mayoría de los incidentes no son causados por “malas consultas” en aislamiento. Son causados por malas consultas en mala concurrencia, o una consulta razonable que de repente obtiene un plan distinto, o un pequeño bloqueo que se convierte en un embotellamiento global.
Broma #1: Una consulta no es “lenta” hasta que alguien la ejecuta en bucle porque acaba de descubrir analíticas.
Guía rápida de diagnóstico (primero/segundo/tercero)
Esta es la secuencia que gana tiempo en incidentes reales. Está sesgada hacia encontrar la clase de cuello de botella rápidamente, luego identificar la huella de consulta específica y después probar la causa.
Primero: clasifica el cuello de botella (60–120 segundos)
- ¿La base de datos está limitada por CPU? Alta CPU de usuario, alta cola de ejecución, latencia del disco estable.
- ¿Está limitada por E/S? CPU moderada pero latencia de lectura/escritura elevada, alto tiempo de fsync, fallos en buffer pool.
- ¿Está limitada por bloqueos? Hilos en ejecución bajos, hilos conectados altos, muchas sesiones “waiting for…”; picos en esperas por bloqueos.
- ¿Está limitada por replicación? El retraso de réplicas aumenta, crecen los relay logs, hilo SQL esperando; o el primario está bien pero las réplicas lentas por volumen de consultas.
Segundo: identifica los principales culpables (2–5 minutos)
- Usa el slow query log (si está activado) para obtener huellas y tiempos.
- Usa Performance Schema para listar sentencias top por tiempo total, tiempo medio y filas examinadas.
- Revisa sesiones activas: qué se está ejecutando ahora y en qué esperan.
Tercero: confirma el modo de fallo (5–15 minutos)
- Para problemas de CPU: verifica regresiones de plan, índices faltantes, predicados pobres, estimaciones de cardinalidad erróneas.
- Para problemas de E/S: verifica proxies de tasa de aciertos del buffer pool, read-ahead, volcados a tablas temporales, presión del redo log, latencia de fsync.
- Para bloqueos: identifica la sesión bloqueante, tipo de bloqueo (fila, metadatos, gap/next-key) y por qué lo mantiene.
- Para replicación: compara mezcla de sentencias primario vs réplica; encuentra la transacción larga o DDL; verifica la configuración de replicación paralela.
No “optimices al azar.” Diagnostica primero, cambia una cosa, mide después. Estás operando un sistema, no haciendo improvisación.
Datos interesantes y contexto histórico (porque los valores por defecto de hoy tienen historia)
- Dato 1: El slow query log de MySQL es anterior a las pilas modernas de observabilidad; es un registro en texto antiguo, pero sigue siendo una de las herramientas más útiles en incidentes porque captura sentencias reales con tiempos.
- Dato 2: Performance Schema empezó siendo una característica controvertida porque las primeras versiones podían ser pesadas si se activaban indiscriminadamente. MySQL moderno lo hizo mucho más práctico como fuente de verdad “siempre activa”—si lo configuras con intención.
- Dato 3: El esquema
sysse creó para hacer Performance Schema usable sin que todos escriban SQL monstruoso. Es básicamente “vistas para humanos”. - Dato 4: Percona popularizó el digest de consultas en el mundo MySQL con herramientas como
pt-query-digest, que normalizaban consultas en huellas mucho antes de que “analytics de consultas” fuera una casilla en un panel. - Dato 5: Los bloqueos de metadatos (MDL) de MySQL son una sorpresa frecuente: un inocente
ALTER TABLEpuede bloquear lecturas de formas que parecen un problema de red hasta que miras las esperas MDL. - Dato 6: El diseño de InnoDB es fuertemente “buffer pool primero”. Si tu working set no cabe y haces lecturas aleatorias, lo sentirás incluso en discos premium—porque la física cobra por milisegundos.
- Dato 7: El retraso en replicación a menudo no es “latencia de red.” Frecuentemente es “el hilo SQL de la réplica no puede seguir” porque la réplica hace demasiado trabajo por transacción, a menudo debido a diferencias de E/S o caché.
- Dato 8: El cambio de tablas temporales basadas en archivos a comportamiento más en memoria entre versiones redujo parte del dolor, pero “tabla temporal en memoria” no es gratis; puede generar presión de memoria, que se convierte en intercambio, que se convierte en incidente.
Tareas prácticas: comandos, salidas y decisiones (12+)
Estas son las tareas que realmente ejecuto durante incidentes o endurecimiento previo. Cada una incluye un comando, qué significa la salida típica y la decisión que tomas.
Task 1: Confirmar en qué servidor estás (y dejar de discutir en Slack)
cr0x@server:~$ mysql -NBe "SELECT VERSION(), @@version_comment;"
8.0.36-28 Percona Server (GPL), Release 28, Revision 1234567
Qué significa: La cadena de versión y el comentario te dicen si es Oracle MySQL, Percona Server, MariaDB, etc. Esto importa porque las funcionalidades y valores por defecto difieren.
Decisión: Elige el playbook correcto. No apliques un flag de afinamiento que no exista en ese build.
Task 2: Comprobar si Performance Schema está activado
cr0x@server:~$ mysql -NBe "SHOW VARIABLES LIKE 'performance_schema';"
performance_schema ON
Qué significa: Si está OFF, perdiste una de las mejores fuentes de “qué está haciendo el servidor ahora mismo”.
Decisión: Si está OFF en producción, planea una ventana de mantenimiento para activarlo y mantenlo ON con ajustes de instrumentación cuidadosos.
Task 3: Ver las sentencias top por latencia total (Performance Schema)
cr0x@server:~$ mysql -t -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms, SUM_ROWS_EXAMINED, SUM_ROWS_SENT FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 48291
total_s: 912.44
avg_ms: 18.89
SUM_ROWS_EXAMINED: 987654321
SUM_ROWS_SENT: 48291
Qué significa: Esta consulta domina el tiempo total y examina un número absurdo de filas por resultado. Ese es tu indicio principal.
Decisión: Investiga indexado y plan. Específicamente, busca un índice compuesto en (user_id, created_at) y confirma que se esté usando.
Task 4: Encontrar “qué se está ejecutando ahora” con sentencias completas
cr0x@server:~$ mysql -t -e "SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, LEFT(INFO,120) AS query FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep' ORDER BY TIME DESC LIMIT 10;"
+------+---------+-------------------+------+---------+------+----------------------------+----------------------------------------------------------+
| ID | USER | HOST | DB | COMMAND | TIME | STATE | query |
+------+---------+-------------------+------+---------+------+----------------------------+----------------------------------------------------------+
| 8123 | appuser | 10.2.3.4:53210 | prod | Query | 98 | Waiting for table metadata | ALTER TABLE orders ADD COLUMN foo INT |
| 8221 | appuser | 10.2.3.9:49211 | prod | Query | 97 | Sending data | SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 50 |
+------+---------+-------------------+------+---------+------+----------------------------+----------------------------------------------------------+
Qué significa: Un DDL está esperando por un metadata lock mientras las consultas continúan, o viceversa. La presencia de “Waiting for table metadata” es una flecha gigante hacia la contención MDL.
Decisión: Si el DDL está bloqueando tráfico crítico, deténlo (o muévelo a herramientas de cambio de esquema en línea). Si el DDL está bloqueado, encuentra la sesión que mantiene el MDL.
Task 5: Identificar bloqueadores de metadata lock (MDL)
cr0x@server:~$ mysql -t -e "SELECT * FROM sys.schema_table_lock_waits WHERE object_schema='prod' AND object_name='orders'\G"
*************************** 1. row ***************************
object_schema: prod
object_name: orders
waiting_query: ALTER TABLE orders ADD COLUMN foo INT
waiting_pid: 8123
blocking_pid: 7991
blocking_query: SELECT COUNT(*) FROM orders WHERE created_at > NOW() - INTERVAL 7 DAY
sql_kill_blocking_query: KILL QUERY 7991
sql_kill_blocking_connection: KILL 7991
Qué significa: Un SELECT de larga duración está manteniendo un metadata lock el tiempo suficiente para bloquear DDL (o viceversa). El esquema sys lo hace legible.
Decisión: Si es seguro, mata la consulta bloqueante (no la conexión salvo que debas). Luego arregla el patrón de la aplicación: transacciones largas y “SELECT … FOR UPDATE” tienden a mantener MDL más tiempo del que la gente piensa.
Task 6: Confirmar que el slow query log está activado y es sensato
cr0x@server:~$ mysql -NBe "SHOW VARIABLES WHERE Variable_name IN ('slow_query_log','slow_query_log_file','long_query_time','log_queries_not_using_indexes');"
slow_query_log ON
slow_query_log_file /var/log/mysql/mysql-slow.log
long_query_time 0.200000
log_queries_not_using_indexes OFF
Qué significa: Se están registrando consultas más lentas de 200ms. Eso es lo suficientemente agresivo para detectar “muerte por mil cortes” pero no tan agresivo como para fundir tu disco.
Decisión: Si está OFF, enciéndelo. Si long_query_time es 10 segundos, estás ciego ante los asesinos comunes.
Task 7: Digerir el slow log en una lista ordenada (Percona Toolkit)
cr0x@server:~$ pt-query-digest /var/log/mysql/mysql-slow.log | head -n 30
# 330ms user time, 20ms system time, 25.00M rss, 80.00M vsz
# Current date: Mon Dec 30 01:12:14 2025
# Overall: 12.34k total, 45 unique, 0.12 QPS, 0.02x concurrency ________
# Time range: 2025-12-30T00:10:00 to 2025-12-30T01:10:00
# Attribute total min max avg 95% stddev median
# ============ ======= ======= ======= ======= ======= ======= =======
# Exec time 8200s 5ms 38s 663ms 2s 1s 120ms
# Rows examine 9.10G 0 12M 737k 3.2M 1.1M 55k
# Profile
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ================== ============= ===== ====== ===== ============
# 1 0xA1B2C3D4E5F6A7B8 4100.0000 50.0% 48291 0.0850 0.10 SELECT orders
Qué significa: Obtienes una lista ordenada por tiempo de respuesta y un ID de consulta normalizado. “Rows examine 9.10G” es una escena del crimen.
Decisión: Concéntrate en las principales 1–3 huellas. No persigas la cola de la distribución durante un incidente.
Task 8: Comprobar la presión del buffer pool de InnoDB (métricas proxy rápidas)
cr0x@server:~$ mysql -t -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name | Value |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests | 987654321 |
| Innodb_buffer_pool_reads | 12345678 |
+---------------------------------------+-----------+
Qué significa: Innodb_buffer_pool_reads son lecturas desde disco; read_requests son lecturas lógicas. Si las lecturas desde disco aumentan respecto a las solicitudes, tu working set está perdiendo caché.
Decisión: Si de repente lees desde disco, identifica qué cambió: un nuevo patrón de consultas, cachés perdidos tras un reinicio o crecimiento de dataset. Considera aumentar buffer pool (si es seguro), pero también arregla la consulta que está escaneando.
Task 9: Identificar esperas de bloqueo a nivel de motor
cr0x@server:~$ mysql -t -e "SELECT * FROM sys.innodb_lock_waits ORDER BY wait_age DESC LIMIT 5\G"
*************************** 1. row ***************************
wait_started: 2025-12-30 01:09:01
wait_age: 00:00:47
locked_table: `prod`.`orders`
locked_index: PRIMARY
waiting_query: UPDATE orders SET status='shipped' WHERE id=?
blocking_query: UPDATE orders SET status='paid' WHERE id=?
blocking_pid: 7442
waiting_pid: 7551
Qué significa: Tienes contención transaccional. Dos updates se pelean por las mismas filas o rangos de índice calientes.
Decisión: Encuentra el patrón. ¿Es una tabla de cola? ¿Un hotspot en la “última fila”? Aplica batching, reordena operaciones, reduce el alcance de la transacción o rediseña el punto de contención.
Task 10: Confirmar si las tablas temporales están desbordando a disco
cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
Created_tmp_disk_tables 184223
Created_tmp_tables 210001
Created_tmp_files 1123
Qué significa: Una alta proporción de tablas temporales en disco sugiere ordenaciones/group-bys costosos o configuración de memoria para tablas temporales insuficiente.
Decisión: Identifica qué consultas crean tablas temporales (Performance Schema puede ayudar), luego corrige la forma de la consulta e índices antes de incrementar memoria a ciegas.
Task 11: Ver los eventos de espera principales (Performance Schema)
cr0x@server:~$ mysql -t -e "SELECT event_name, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, COUNT_STAR FROM performance_schema.events_waits_summary_global_by_event_name ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;"
+------------------------------------------+---------+------------+
| event_name | total_s | COUNT_STAR |
+------------------------------------------+---------+------------+
| wait/io/file/innodb/innodb_data_file | 820.11 | 91234567 |
| wait/synch/mutex/innodb/buf_pool_mutex | 210.44 | 61234567 |
| wait/lock/metadata/sql/mdl | 98.22 | 120345 |
+------------------------------------------+---------+------------+
Qué significa: Tu tiempo se está yendo a E/S de archivos y contención de mutex del buffer pool, con un suplemento de locks de metadatos. Eso forma una narrativa útil.
Decisión: Si E/S domina, busca escaneos y volcados. Si los mutex dominan, revisa concurrencia, instancias de buffer pool y páginas calientes. Si MDL domina, arregla la programación de DDL y las transacciones largas.
Task 12: Detectar retraso de replicación y quién tiene la culpa
cr0x@server:~$ mysql -t -e "SHOW REPLICA STATUS\G" | egrep -i "Seconds_Behind_Source|Replica_SQL_Running_State|Last_SQL_Error|Retrieved_Gtid_Set|Executed_Gtid_Set"
Seconds_Behind_Source: 187
Replica_SQL_Running_State: Waiting for dependent transaction to commit
Last_SQL_Error:
Retrieved_Gtid_Set: 1-1000023
Executed_Gtid_Set: 1-999842
Qué significa: La réplica está retrasada. El hilo SQL está esperando commit (a menudo presión de commit/flush o dependencias).
Decisión: Revisa latencia de commit y transacciones largas. Considera ajustar configuraciones de durabilidad sólo con aceptación explícita de riesgo. Más a menudo: arregla el patrón transaccional que creó el retraso.
Task 13: Correlacionar tiempo de consulta con filas examinadas (encontrar “parece rápido” pero no lo es)
cr0x@server:~$ mysql -t -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms, ROUND(SUM_ROWS_EXAMINED/COUNT_STAR,0) AS rows_examined_per_call FROM performance_schema.events_statements_summary_by_digest WHERE COUNT_STAR > 1000 ORDER BY rows_examined_per_call DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT id FROM sessions WHERE token = ?
COUNT_STAR: 450000
avg_ms: 1.20
rows_examined_per_call: 12000
Qué significa: La latencia media es baja, pero examina 12k filas por llamada. Bajo carga, esto se convierte en un triturador de CPU y buffer pool.
Decisión: Añade o arregla un índice en token. Este es trabajo preventivo que evita un incidente futuro.
Task 14: Verificar elección de plan con EXPLAIN (y no confíes en tu intuición)
cr0x@server:~$ mysql -t -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 50;"
+----+-------------+--------+------------+------+--------------------------+--------------------------+---------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | rows | filtered | Extra |
+----+-------------+--------+------------+------+--------------------------+--------------------------+---------+------+----------+-------------+
| 1 | SIMPLE | orders | NULL | ref | idx_user_created,idx_user| idx_user_created | 8 | 50 | 100.00 | Using index |
+----+-------------+--------+------------+------+--------------------------+--------------------------+---------+------+----------+-------------+
Qué significa: Usa el índice compuesto y lee ~50 filas. Si tus métricas anteriores mostraban millones examinadas, estás viendo una forma de consulta diferente, sesgo de parámetros o inestabilidad de plan.
Decisión: Compara el texto exacto de la sentencia y los patrones de parámetros. Investiga histogramas/estadísticas y si las consultas son sargables.
Task 15: Comprobar latencia de E/S del sistema de archivos en el momento (porque “el disco está bien” es una mentira hasta probarlo)
cr0x@server:~$ iostat -x 1 3
Linux 6.5.0 (db01) 12/30/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
32.10 0.00 6.20 18.40 0.00 43.30
Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 820.0 1200.0 38.2 95.1 92.1 8.20 12.40 9.10 14.60 0.35 72.0
Qué significa: 12ms de await en NVMe es sospechoso, con iowait al 18%. Eso es una historia de E/S, no “sólo CPU”.
Decisión: Sube en la pila: qué consultas están causando amplificación de lectura o volcados temporales? Si es almacenamiento compartido, revisa vecinos ruidosos.
Task 16: Si tienes PMM: confirma que QAN está ingiriendo y muestra las consultas top
cr0x@server:~$ pmm-admin list
pmm-admin 2.41.0
Service type Service name Address and Port Service ID
MySQL db01 127.0.0.1:3306 /service_id/abcd1234
Agent type Status Agent ID
pmm-agent Running /agent_id/efgh5678
Qué significa: PMM ve el servicio MySQL. Si Query Analytics no muestra datos, suele ser un problema de collector/config, no “no existen slow queries”.
Decisión: Usa PMM QAN para pivotar de huella → ejemplos → hosts → rango temporal. Es el camino más corto del dolor al culpable.
Huellas de consultas críticas: cómo reconocer la clase de problema
1) El “tsunami de filas examinadas”
Firma: Alto SUM_ROWS_EXAMINED, filas devueltas moderadas, alta latencia total. La CPU sube, las lecturas del buffer pool suben y pueden seguir lecturas de disco.
Causas: Índice compuesto faltante, predicados no sargables, conversión implícita de tipos, wildcard inicial en LIKE '%foo', o una consulta reescrita por un ORM en algo que el optimizador no puede amar.
Arreglo: Crea el índice correcto, reescribe el predicado para que sea sargable, alinea tipos y verifica con EXPLAIN. Luego vigila regresiones de plan tras cambios en estadísticas.
2) El “convoy de bloqueos”
Firma: Muchas sesiones en estados “Waiting for…”, baja CPU, throughput colapsado. La base de datos parece inactiva mientras todos esperan.
Causas: Filas calientes, actualizaciones serializadas, transacciones largas, gap locks bajo REPEATABLE READ, o DDL colisionando con tráfico vía MDL.
Arreglo: Reduce el alcance de las transacciones, reordena operaciones, añade índices para reducir rangos bloqueados, usa patrones de cambio de esquema en línea y programa DDL como cambios de producción (porque lo son).
3) La “fábrica de volcados a tablas temporales”
Firma: Aumento de Created_tmp_disk_tables, incremento de latencia de E/S, consultas con GROUP BY/ORDER BY más lentas y discos más activos.
Causas: Índices faltantes para ordenación/agrupación, selección de demasiadas columnas, conjuntos de resultados grandes o configuraciones de memoria para tablas temporales insuficientes.
Arreglo: Indexar para satisfacer ORDER BY/GROUP BY, reducir columnas seleccionadas, preagregar o refactorizar. Aumenta memoria sólo después de detener la hemorragia.
4) El “retraso de replicación disfrazado de lentitud de la app”
Firma: Lecturas dirigidas a réplicas se vuelven más lentas o desactualizadas; o failovers se vuelven riesgosos por alto retraso.
Causas: Una gran transacción, presión de commit, dependencias o una réplica con perfil I/O/caché distinto al primario.
Arreglo: Fragmenta escrituras grandes, ajusta el paralelismo de replicación apropiadamente y asegura que réplicas tengan rendimiento de almacenamiento comparable (o ajusta el enrutamiento esperado).
5) El “plan cambió y nadie lo notó”
Firma: Misma huella de consulta, peor de repente. EXPLAIN muestra índice o orden de join distinto al de la semana pasada.
Causas: Estadísticas cambiaron, distribución de datos cambió o una actualización de versión alteró el comportamiento del optimizador.
Arreglo: Actualiza estadísticas intencionalmente, considera histogramas, estabiliza con hints sólo si es necesario y monitorea deriva de planes para consultas críticas.
Broma #2: El optimizador es como un gato: a veces brillante, a veces dormido sobre el teclado, y no se explicará a sí mismo.
Tres microhistorias corporativas desde las trincheras
Microhistoria 1: El incidente causado por una suposición equivocada
La compañía tenía una separación limpia: escrituras al primario, lecturas a las réplicas. La suposición era simple y reconfortante: “El tráfico de lectura no puede perjudicar las escrituras.” Todos lo repetían hasta que se volvió política.
Entonces el flujo de checkout empezó a agotar tiempo. La CPU del primario no estaba al máximo, pero la latencia de commit subió. Las réplicas estaban bien. La red estaba bien. El equipo de aplicación juró que eran “sólo algunas lecturas extra”.
Performance Schema mostró la verdad: un nuevo endpoint “sólo lectura” ejecutaba un join multi-tabla con un predicado faltante, escaneando grandes rangos. Se ejecutaba en el primario en una ruta de fallback cuando el pool de lecturas se agotaba. La consulta en sí no era tan lenta por ejecución, sólo lo suficientemente frecuente para expulsar páginas calientes y crear churn en el buffer pool.
La suposición equivocada no era sobre “lecturas”. Era sobre dónde suceden las lecturas bajo fallo parcial. Exhaustión de pool de conexiones, retraso de réplicas, fallas de DNS—cualquiera puede enrutar lecturas al primario. La consulta se volvió un problema en la ruta de escritura al destrozar la localización de caché e incrementar E/S.
Lo solucionaron añadiendo el predicado faltante y un índice, luego implementaron un circuito de seguridad: si las réplicas no están disponibles, degradar la funcionalidad en lugar de martillar silenciosamente el primario. La monitorización se actualizó para alertar sobre “huellas de consulta de lectura en primario” como riesgo de primera clase.
Microhistoria 2: La optimización que salió mal
Un equipo quería reducir p99 para un endpoint de búsqueda. Añadieron un índice que parecía obviamente correcto, y mejoró su consulta de prueba. Deploy en producción, felicitaciones.
Dos días después, la carga de escritura empezó a atascarse en ráfagas. La CPU subió, luego bajó, pero el throughput se volvió errático. El slow query log no mostraba una “consulta mala”. Ese fue el indicio: el dolor no estaba en el tiempo de ejecución de consulta; estaba en la amplificación de escrituras.
El nuevo índice multiplicó el coste de las actualizaciones. Un job en background que tocaba muchas filas ahora mantenía una estructura extra con mala localidad. El buffer pool se vio churneado por páginas de índice. Performance Schema mostró aumentos en contención de mutex alrededor de estructuras del buffer pool, e iostat mostró latencia de escritura elevada durante las ráfagas.
La solución no fue “quitar índices”. Fue: hacer que el job sea menos destructivo (batching, menor concurrencia) y rediseñar el índice para coincidir con predicados reales. Reemplazaron un índice ancho por uno compuesto más estrecho que satisfacía la ruta de lectura sin penalizar tanto las escrituras.
Lección: “Optimizar” es un cambio de trade-offs. Si no mides la ruta de escritura, no estás optimizando—estás apostando.
Microhistoria 3: La práctica aburrida pero correcta que salvó el día
Otra organización tenía un ritual del que nadie se jactaba: mantenían slow query logging activado con umbral bajo, rotaban logs correctamente y digerían resultados semanalmente. Nada heroico, sólo higiene.
Un viernes por la tarde, la latencia p95 subió. El on-call abrió PMM Query Analytics y vio una nueva huella en la cima en un minuto. Era una consulta añadida en un release menor: una búsqueda por columna token, pero el token se almacenaba con un tipo distinto al parámetro, provocando conversión implícita y matando el uso de índice.
Porque los slow logs y digest eran rutina, tenían líneas base. Pudo probar que “esta consulta no existía ayer” y asociarla a un despliegue. El rollback fue inmediato. Luego el equipo añadió el índice correcto y alineó tipos en el parche siguiente.
Sin sala de guerra. Sin all-hands. La solución fue casi aburrida. Ese es el punto: la observabilidad aburrida previene outages emocionantes.
Idea parafraseada de Gene Kranz: duro y competente
—mantén la calma, sigue el procedimiento y usa datos en lugar de intuiciones.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: CPU alta, pero slow query log silencioso
Causa raíz: Muchas consultas medianamente lentas bajo el umbral, o sobrecarga en parseo/ejecución no capturada por un long_query_time alto.
Solución: Baja long_query_time a 200–500ms temporalmente, o activa agregación de digest de Performance Schema y ordena por tiempo total.
2) Síntoma: Threads connected alto, Threads running bajo, latencia terrible
Causa raíz: Esperas por bloqueos o recursos (E/S, bloqueos de metadatos). Todos están en cola.
Solución: Usa vistas sys para esperas de bloqueo y MDL; identifica el bloqueador; reduce la duración de transacciones; programa DDL correctamente.
3) Síntoma: Consultas se vuelven “aleatoriamente” lentas tras reinicio
Causa raíz: Buffer pool frío; working set no calentado; lecturas desde disco explotan.
Solución: Espera y planifica (estrategias de warmup, buffer pool suficiente, margen de capacidad). Verifica también que no perdiste localidad de caché por nuevos escaneos.
4) Síntoma: Réplica lag aumenta durante un job por lotes
Causa raíz: Transacciones grandes o alta tasa de commits saturan capacidad de aplicar/flush de la réplica, o dependencias limitan el paralelismo.
Solución: Rompe escrituras en transacciones más pequeñas, ajusta concurrencia del job y asegura que la durabilidad y almacenamiento de la réplica coincidan con la intención.
5) Síntoma: Aparece “Waiting for table metadata lock” durante despliegues
Causa raíz: DDL colisiona con consultas/transacciones largas; el MDL se mantiene más tiempo de lo esperado.
Solución: Elimina transacciones largas, ejecuta cambios de esquema con herramientas online, y aplica ventanas/controles para DDL.
6) Síntoma: p99 empeora, medias están bien
Causa raíz: Contención, encolamiento, sesgo de parámetros o cambios intermitentes de plan. La media miente.
Solución: Usa percentiles en QAN/monitorización, revisa sentencias top por latencia máxima y por percentil 95, e inspecciona ejemplos específicos de consultas.
7) Síntoma: Utilización de disco no está al 100%, pero la latencia es alta
Causa raíz: Profundidad de cola y latencia pueden subir mucho antes de que %util llegue al 100% (especialmente en almacenamiento compartido). I/O aleatorio pequeño es el asesino silencioso.
Solución: Confía en await y profundidad de cola. Reduce amplificación de lectura (índices, forma de consulta), reduce volcados temporales y verifica la salud del almacenamiento.
8) Síntoma: “Arreglaste” una consulta con un índice, pero el throughput general bajó
Causa raíz: Sobrecoste de mantenimiento de índices en escrituras, especialmente en tablas de alta rotación.
Solución: Reevalúa diseño de índices, considera índices compuestos/narrow, reduce churn de escrituras y prueba ambos caminos de lectura y escritura.
Listas de verificación / plan paso a paso
Checklist A: Monitorización base que deberías tener antes del próximo incidente
- Activa Performance Schema y mantenlo activado.
- Activa slow query log con un umbral que capture dolor real (a menudo 200–500ms, ajusta según carga).
- Rota y conserva slow logs; asegúrate de que el disco no se llene.
- Ejecuta un reporte de digest diario/semanal y sigue las huellas top a lo largo del tiempo.
- Recopila métricas de eventos de espera y ten al menos un panel que las clasifique.
- Monitorea retraso de replicación y alerta por tendencia, no sólo por valor absoluto.
- Captura ejemplos de consultas para las huellas principales (PMM ayuda, pero puedes almacenar muestras manualmente).
Checklist B: Pasos de respuesta a incidentes (versión 15 minutos)
- Clasifica: CPU vs E/S vs bloqueos vs replicación.
- Obtén digests top por tiempo total y por tiempo medio.
- Revisa sesiones activas y esperas por bloqueo; encuentra bloqueadores.
- Si es E/S: revisa tablas temporales en disco y escaneos (rows examined).
- Valida el plan para la huella top con EXPLAIN.
- Elige una acción: matar una consulta bloqueante, añadir un índice (sólo si es seguro y probado), limitar un job o revertir un deploy.
- Mide de nuevo. Si la métrica no se movió, tu arreglo no fue el correcto.
Checklist C: Cambios de endurecimiento que reducen riesgo futuro de “consultas asesinas”
- Añade guardrails en la app: timeouts, circuit breakers y límites de concurrencia de consultas.
- Prefiere sentencias preparadas y formas de consulta estables (ayuda al digesting y reduce caos de planes).
- Estandariza la práctica de cambios de esquema (enfoque de cambio de esquema online, conciencia MDL).
- Haz de “filas examinadas por llamada” un SLO rastreado para endpoints críticos.
- Escribe un plan de rollback para cada cambio que afecte consultas (los índices cuentan).
Preguntas frecuentes
1) ¿Percona Server es “mejor” que MySQL para monitorización?
Es mejor en el sentido en que una buena caja de herramientas es mejor que una llave inglesa. MySQL tiene los primitivos; el ecosistema de Percona tiende a hacerlos operativos más rápido, especialmente con PMM y la cultura de herramientas.
2) ¿Debo confiar en slow query log o Performance Schema?
Usa ambos. El slow log te da sentencias exactas y tiempos en un archivo durable. Performance Schema te da verdad agregada en vivo y contexto de esperas. Cuando coinciden, puedes moverte rápido.
3) ¿Qué valor de long_query_time debería usar?
Empieza alrededor de 200–500ms para servicios OLTP y ajusta según volumen y almacenamiento. Si tu objetivo p95 es 50ms, un umbral de 10 segundos es negligencia observacional.
4) ¿Habilitar Performance Schema perjudica el rendimiento?
Puede, si activas todos los instrumentos y consumidores. Con valores sensatos y instrumentación selectiva, generalmente es aceptable en producción. Mide la sobrecarga en staging si te preocupa; no lo desactives por superstición.
5) ¿Cómo encuentro “consultas asesinas” cuando todo es rápido individualmente?
Ordena por tiempo total y llamadas, no sólo por latencia media. Una consulta de 5ms ejecutada 50,000 veces por minuto puede comerse todo tu presupuesto de CPU.
6) ¿Cuál es la forma más rápida de diagnosticar problemas de bloqueo?
Usa vistas sys: sys.innodb_lock_waits y sys.schema_table_lock_waits. Encuentra el PID bloqueador, mira su consulta y edad de transacción y decide si matar o arreglar el patrón de carga.
7) ¿Por qué veo retraso de replicación pero el primario parece sano?
Aplicar en la réplica es una carga distinta: caché distinto, I/O distinto, a veces mezcla de consultas distinta. El retraso suele ser causado por una transacción grande o presión de commits, no por estrés del primario.
8) ¿PMM puede reemplazar mi monitorización existente?
La complementa. Aún necesitas métricas del host, logs y disciplina de alertas. PMM destaca en análisis de consultas y visibilidad específica de bases de datos, que suele ser la pieza que falta en incidentes.
9) ¿Debo matar las “consultas asesinas” durante incidentes?
A veces. Si una consulta está bloqueando a otras o causando un convoy de bloqueos, matarla puede restaurar el servicio rápido. Pero toma la decisión de forma explícita: matar consulta vs matar conexión, y entiende las tormentas de reintentos desde la aplicación.
10) ¿Y si la “consulta asesina” son muchas consultas similares?
Exactamente por eso importan los digests. Arregla el patrón: añade el índice correcto, cambia la forma de la consulta o añade caching. No golpees sentencias individuales como si fueran topos.
Conclusión: pasos siguientes que puedes hacer esta semana
Si quieres dejar de adivinar, tu objetivo es simple: lograr que la base de datos se explique a sí misma rápidamente.
- Activa el registro lento con un umbral realista y rótalo.
- Mantén Performance Schema activado, y usa vistas del esquema sys para no tener que escribir SQL arqueológico.
- Adopta un flujo de trabajo de digest de consultas: ranking diario/semanal, seguimiento de huellas top y trata “filas examinadas por llamada” como un olor a problema.
- Elige un panel de “diagnóstico rápido”: huellas top, esperas top, sesiones activas, retraso de replicación. Si no está ahí, no existirá durante un incidente.
- Decide tu postura sobre Percona: si necesitas visibilidad operativa más rápida con menos pegamento bespoke, Percona Server + PMM es pragmático. Si estás comprometido con MySQL upstream, copia la misma disciplina con tus propias herramientas.
La mayoría de las consultas asesinas no son ingeniosas. Son consultas ordinarias en condiciones extraordinarias. Tu trabajo es hacer visibles esas condiciones y volver al culpable dolorosamente fácil de nombrar.