En production, MySQL ne tombe généralement pas en panne avec fracas. Il échoue par lassitude : la latence p95 double discrètement, le CPU semble « correct » et l’équipe produit jure « nous n’avons rien déployé ». Pendant ce temps, la base de données est en feu — poliment.
La différence entre un incident de deux heures et une correction en dix minutes n’est presque jamais une question de compétence brute. C’est de savoir si votre supervision vous dit quelle requête est le méchant, pourquoi elle est lente (CPU, E/S, verrous ou réplication) et ce qui a changé.
Ce que vous essayez réellement de faire (et pourquoi les suppositions coûtent cher)
« Trouver les requêtes tueuses » semble être un objectif unique, mais en exploitation c’est en fait trois tâches distinctes :
- Détection : identifier la poignée de statements qui dominent la latence, le CPU, les E/S ou le temps de verrou.
- Attribution : relier ces statements à une action applicative, un déploiement, un feature flag, un cron job, un notebook d’analyste ou un ORM mal configuré.
- Actionnable : choisir la bonne correction — index, réécriture de requête, stabilité du plan, limites de concurrence, buffering ou changements de schéma — sans tout casser.
Le piège des « suppositions » arrive quand votre monitoring ne montre que des métriques hôtes (CPU, disque, réseau). Elles sont nécessaires, pas suffisantes. Les métriques hôtes vous disent que la base est mécontente. Elles ne vous disent pas qui la rend mécontente.
Vous voulez trois couches de visibilité :
- Couche SQL : empreintes de requêtes normalisées, distribution de latence, lignes examinées vs renvoyées, tables temporaires, merges de tri, erreurs.
- Couche attente : temps passé sur verrous, E/S, verrous metadata, buffer pool, redo log et mutex internes.
- Couche système : saturation CPU, file d’exécution, latence E/S, temps de fsync, pression sur le cache de pages, retransmissions réseau.
MySQL peut fournir tout cela. Percona Server aussi. La différence pratique est ce que vous pouvez faire avant un incident, combien coûte l’instrumentation, et à quel point il est facile d’assembler les preuves dans une chronologie.
MySQL vs Percona Server : ce qui change pour la surveillance
Ce sont des cousins, pas des étrangers
Percona Server for MySQL est une build en aval qui suit de près MySQL en amont, avec de l’instrumentation supplémentaire et des fonctionnalités opérationnelles. Dans des flottes modernes, vous verrez :
- Oracle MySQL (community ou enterprise) : fonctionnalités de base, Performance Schema, schéma sys, slow log, EXPLAIN, optimizer trace, etc.
- Percona Server : compatible en amont, plus des améliorations opérationnelles et des réglages supplémentaires (certains sont moins « uniques » qu’avant, mais restent importants en production).
- Percona Monitoring and Management (PMM) : ce n’est pas un fork de serveur, mais une stack de monitoring qui rend l’analyse des requêtes utilisable par des humains.
Différences de monitoring qui comptent en pratique
La grande question n’est pas « lequel est plus rapide ? » mais « lequel rend la cause racine évidente sous contrainte ? » Voici mon évaluation :
- Expérience d’analyse des requêtes : avec MySQL vanilla, vous pouvez y arriver avec slow logs + Performance Schema + tableaux de bord maison. Avec Percona Server + PMM, vous atteignez généralement « top 10 des empreintes de requêtes les plus lourdes » plus vite et avec moins de bricolage.
- Contrôle du coût d’instrumentation : les deux peuvent être configurés pour éviter un overhead important, mais l’écosystème Percona tend à faire de « faible overhead, always on » une norme culturelle plutôt qu’une diapositive inspirante.
- Métriques et réglages supplémentaires : Percona Server a historiquement livré des variables d’état et des fonctionnalités additionnelles autour de l’instrumentation et du diagnostic. Même quand l’amont rattrape son retard, Percona garde une bias opérationnelle : exposer les arêtes vives, ne pas les cacher.
Conseil d’opinion : si vous exploitez MySQL à une échelle modeste et que vous avez déjà une bonne discipline d’observabilité, MySQL en amont suffit. Si vous opérez à une échelle « pourquoi c’est toujours urgent » et que vous voulez une analyse de requêtes utilisable par votre on-call à 3 h du matin, Percona Server + PMM est difficile à battre.
Une vérité sèche : la plupart des incidents ne sont pas causés par des « mauvaises requêtes » isolées. Ils sont causés par des mauvaises requêtes à forte concurrence, ou une requête raisonnable qui obtient soudainement un plan différent, ou un petit verrou qui devient un embouteillage global.
Blague #1 : Une requête n’est pas « lente » tant qu’elle n’est pas exécutée en boucle par quelqu’un qui vient de découvrir l’analytics.
Plan de diagnostic rapide (premier/deuxième/troisième)
Voici la séquence qui fait gagner du temps en incidents réels. Elle est biaisée pour trouver rapidement la classe de goulot d’étranglement, puis identifier l’empreinte de requête spécifique, puis prouver la cause.
Premier : classer le goulot d’étranglement (60–120 secondes)
- La base est-elle CPU-bound ? CPU utilisateur élevé, file d’exécution élevée, latence disque stable.
- Est-ce lié aux E/S ? CPU modéré mais latence lecture/écriture élevée, temps de fsync élevé, misses du buffer pool.
- Est-ce lié aux verrous ? Peu de threads en exécution, beaucoup de threads connectés, nombreuses sessions « Waiting for… » ; pics d’attente de verrous.
- Est-ce lié à la réplication ? Le lag des replicas augmente, les relay logs grossissent, le SQL thread attend ; ou le primaire va bien mais les replicas sont lents à cause du volume de requêtes.
Deuxième : identifier les principaux coupables (2–5 minutes)
- Utilisez le slow query log (si activé) pour obtenir des empreintes et des timings.
- Utilisez le Performance Schema pour lister les statements en tête par temps total, temps moyen et lignes examinées.
- Vérifiez les sessions actives : que s’exécute-t-il maintenant et sur quoi attendent-elles ?
Troisième : confirmer le mode de défaillance (5–15 minutes)
- Pour les problèmes CPU : vérifier les régressions de plan, index manquants, prédicats inefficaces, mauvaises estimations de cardinalité.
- Pour les problèmes E/S : vérifier les proxies de hit rate du buffer pool, le read-ahead, les déversements de tables temporaires, la pression sur le redo log, la latence de fsync.
- Pour les verrous : identifier la session bloquante, le type de verrou (ligne, metadata, gap/next-key) et pourquoi elle le détient.
- Pour la réplication : comparer le mix de statements primaire vs replica ; trouver la transaction longue ou le DDL ; vérifier la configuration de réplication parallèle.
Ne faites pas d’« optimisation aléatoire ». Diagnostiquez d’abord, changez une chose, puis mesurez. Vous administrez un système, pas un cours d’improvisation.
Faits intéressants et contexte historique (parce que les valeurs par défaut ont une histoire)
- Fait 1 : Le slow query log de MySQL existait bien avant les stacks modernes d’observabilité ; c’est un journal textuel old-school, mais c’est toujours l’un des outils les plus utiles en incident car il capture de vrais statements avec les timings.
- Fait 2 : Performance Schema a commencé comme une fonctionnalité controversée car les premières versions pouvaient être lourdes si activées sans précaution. Les versions modernes de MySQL l’ont rendu bien plus pratique comme source de vérité « always on » — si vous le configurez avec intention.
- Fait 3 : Le schéma
sysa été créé pour rendre Performance Schema utilisable sans que chacun écrive son propre SQL monstrueux. C’est essentiellement des « vues pour humains ». - Fait 4 : Percona a popularisé le digesting des requêtes dans l’écosystème MySQL avec des outils comme
pt-query-digest, qui normalisaient les requêtes en empreintes bien avant que « query analytics » ne devienne une case à cocher sur un tableau de bord. - Fait 5 : Les verrous metadata (MDL) de MySQL surprennent souvent : un
ALTER TABLEapparemment inoffensif peut bloquer des lectures d’une manière qui ressemble à un problème réseau jusqu’à ce que vous regardiez les attentes MDL. - Fait 6 : Le design d’InnoDB est avant tout centré sur le « buffer pool ». Si votre working set ne tient pas et que vous effectuez des lectures aléatoires, vous le ressentirez même sur des disques haut de gamme — car la physique facture au milliseconde près.
- Fait 7 : Le lag de réplication n’est souvent pas un « lag réseau ». Il s’agit fréquemment d’un « SQL thread qui ne suit pas » parce que le replica fait trop de travail par transaction, souvent à cause de différences d’E/S ou de cache.
- Fait 8 : Le passage de tables temporaires sur disque vers un comportement plus en mémoire selon les versions a réduit certaines douleurs, mais « table temporaire en mémoire » n’est pas gratuite ; cela peut créer de la pression mémoire, puis du swapping, puis un incident.
Tâches pratiques : commandes, sorties et décisions (12+)
Voici les tâches que j’exécute réellement pendant les incidents ou pour le durcissement préventif. Chacune inclut une commande, ce que signifie une sortie typique et la décision que vous prenez.
Task 1: Confirm which server you’re on (and stop arguing in Slack)
cr0x@server:~$ mysql -NBe "SELECT VERSION(), @@version_comment;"
8.0.36-28 Percona Server (GPL), Release 28, Revision 1234567
Ce que cela signifie : La chaîne de version et le commentaire vous indiquent s’il s’agit d’Oracle MySQL, Percona Server, MariaDB, etc. C’est important car les fonctionnalités et les valeurs par défaut diffèrent.
Décision : Choisir le playbook adapté. N’appliquez pas un flag de tuning qui n’existe pas sur ce build.
Task 2: Check whether Performance Schema is enabled
cr0x@server:~$ mysql -NBe "SHOW VARIABLES LIKE 'performance_schema';"
performance_schema ON
Ce que cela signifie : S’il est OFF, vous venez de perdre l’une des meilleures sources de « ce que fait le serveur maintenant ».
Décision : S’il est OFF en production, planifiez une fenêtre de maintenance pour l’activer, et gardez-le ON avec des réglages d’instrumentation prudents.
Task 3: See top statements by total latency (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
Ce que cela signifie : Cette requête domine le temps total et examine un nombre absurde de lignes par résultat. C’est votre piste principale.
Décision : Examiner l’indexation et le plan. Plus précisément, vérifier la présence d’un index composite sur (user_id, created_at) et confirmer qu’il est utilisé.
Task 4: Find “what is running right now” with full statements
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 |
+------+---------+-------------------+------+---------+------+----------------------------+----------------------------------------------------------+
Ce que cela signifie : Un DDL attend un MDL pendant que des requêtes continuent, ou l’inverse. La présence de « Waiting for table metadata » indique fortement une contention MDL.
Décision : Si le DDL bloque un trafic critique, stoppez-le (ou migrez-le vers un outil de changement de schéma en ligne). Si le DDL est bloqué, trouvez la session qui détient le MDL.
Task 5: Identify metadata lock blockers (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
Ce que cela signifie : Un SELECT long maintient un MDL assez longtemps pour bloquer un DDL (ou l’inverse). Le schéma sys rend cela lisible.
Décision : Si c’est sûr, kill la requête bloquante (pas la connexion sauf nécessité). Puis corriger le pattern applicatif : les longues transactions et les « SELECT … FOR UPDATE » tiennent le MDL plus longtemps qu’on ne le pense.
Task 6: Confirm slow query log is enabled and sensible
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
Ce que cela signifie : Journalisation des requêtes plus lentes que 200ms. C’est assez agressif pour détecter une « mort par mille coupures » mais pas au point de noyer le disque.
Décision : S’il est OFF, activez-le. Si long_query_time est à 10 secondes, vous êtes aveugle aux tueurs courants.
Task 7: Digest the slow log into a ranked list (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
Ce que cela signifie : Vous obtenez une liste classée par temps de réponse et un ID de requête normalisé. « Rows examine 9.10G » est une scène de crime.
Décision : Concentrez-vous sur les 1–3 empreintes principales. Ne poursuivez pas la queue de la distribution pendant un incident.
Task 8: Check InnoDB buffer pool pressure (quick proxy metrics)
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 |
+---------------------------------------+-----------+
Ce que cela signifie : Innodb_buffer_pool_reads sont des lectures depuis le disque ; read_requests sont des lectures logiques. Si les lectures disque augmentent par rapport aux requêtes, votre working set manque de cache.
Décision : Si vous lisez soudainement depuis le disque, identifiez ce qui a changé : nouveau pattern de requête, caches perdus après un redémarrage, ou croissance du dataset. Envisagez d’augmenter le buffer pool (si sûr), mais corrigez aussi la requête qui scanne.
Task 9: Identify lock waits at the engine level
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
Ce que cela signifie : Vous avez une contention transactionnelle. Deux updates se battent sur les mêmes lignes ou des plages d’index chaudes.
Décision : Trouvez le pattern. Est-ce une table de queue ? Un hotspot « dernière ligne » ? Appliquez le batching, réordonnez les opérations, réduisez la portée des transactions ou repensez le point de contention.
Task 10: Confirm whether temp tables are spilling to disk
cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
Created_tmp_disk_tables 184223
Created_tmp_tables 210001
Created_tmp_files 1123
Ce que cela signifie : Un ratio élevé de tables temporaires sur disque suggère des sorts/group-bys coûteux ou une configuration mémoire de tables temporaires insuffisante.
Décision : Identifiez quelles requêtes créent des tables temporaires (Performance Schema peut aider), puis corrigez la forme des requêtes et les index avant d’augmenter aveuglément les limites mémoire.
Task 11: Look at top wait events (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 |
+------------------------------------------+---------+------------+
Ce que cela signifie : Votre temps va dans les E/S fichiers et la contention sur le mutex du buffer pool, avec un peu de verrous metadata. C’est un récit utile.
Décision : Si les E/S dominent, cherchez des scans et des déversements. Si les mutex dominent, vérifiez la concurrence, les innodb_buffer_pool_instances et les pages chaudes. Si MDL domine, corrigez la planification des DDL et les longues transactions.
Task 12: Spot replication lag and who is to blame
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
Ce que cela signifie : Le replica est en retard. Le SQL thread attend le commit (souvent pression de commit/flush ou contraintes de dépendance).
Décision : Vérifiez la latence de commit et les longues transactions. Envisagez d’ajuster les paramètres de durabilité uniquement en acceptant explicitement le risque. Le plus souvent : corriger le pattern transactionnel qui a créé le lag.
Task 13: Correlate query time with rows examined (find “looks fast” but isn’t)
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
Ce que cela signifie : La latence moyenne est faible, mais elle examine 12k lignes par appel. Sous charge, cela devient un broyeur de CPU et du buffer pool.
Décision : Ajouter ou corriger un index sur token. C’est un travail préventif qui évite un incident futur.
Task 14: Verify plan choice with EXPLAIN (and don’t trust your intuition)
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 |
+----+-------------+--------+------------+------+--------------------------+--------------------------+---------+------+----------+-------------+
Ce que cela signifie : Il utilise l’index composite et lit ~50 lignes. Si vos métriques antérieures montraient des millions examinées, vous regardez soit une forme de requête différente, un skew de paramètres, ou une instabilité de plan.
Décision : Comparez le texte exact de la requête et les patterns de paramètres. Examinez les histogrammes/statistiques et si les requêtes sont sargables.
Task 15: Check filesystem I/O latency in the moment (because “disk is fine” is a lie until proven)
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
Ce que cela signifie : 12ms d’attente sur NVMe est suspect, avec un iowait à 18 %. C’est une histoire d’E/S, pas « juste du CPU ».
Décision : Remontez la pile : quelles requêtes provoquent l’amplification de lecture ou les déversements temporaires ? Si le stockage est partagé, vérifiez les voisins bruyants.
Task 16: If you have PMM: confirm QAN is ingesting and show top queries
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
Ce que cela signifie : PMM voit le service MySQL. Si Query Analytics n’affiche pas de données, il s’agit généralement d’un problème de collecteur/config, pas de « pas de slow queries existantes ».
Décision : Utilisez PMM QAN pour pivoter de l’empreinte → exemples → hôtes → plage temporelle. C’est le chemin le plus court de la douleur au coupable.
Empreintes des requêtes tueuses : comment reconnaître la classe de problème
1) Le « tsunami de lignes examinées »
Signature : Fort SUM_ROWS_EXAMINED, peu de lignes renvoyées, latence totale élevée. Le CPU monte, les lectures du buffer pool augmentent, les lectures disque peuvent suivre.
Causes : Index composite manquant, prédicats non-sargables, conversion implicite de type, LIKE '%foo' avec wildcard en tête, ou une requête réécrite par l’ORM en quelque chose que l’optimiseur ne peut pas aimer.
Correction : Créez le bon index, réécrivez le prédicat pour qu’il soit sargable, alignez les types et vérifiez avec EXPLAIN. Ensuite surveillez les régressions de plan après les changements de stats.
2) Le « convoi de verrous »
Signature : Beaucoup de sessions en état « Waiting for… », CPU bas, débit effondré. La base a l’air inactive alors que tout le monde attend.
Causes : Lignes chaudes, mises à jour sérialisées, longues transactions, gap locks sous REPEATABLE READ, ou DDL qui entre en collision avec le trafic via MDL.
Correction : Réduire la portée des transactions, réordonner les opérations, ajouter des index pour réduire les plages verrouillées, utiliser des patterns de changement de schéma en ligne, et planifier les DDL comme des changements de production (parce que ça en est).
3) L’« usine de déversement de tables temporaires »
Signature : Augmentation de Created_tmp_disk_tables, latence E/S en hausse, requêtes avec GROUP BY/ORDER BY plus lentes, disques très sollicités.
Causes : Index manquants pour le tri/regroupement, sélection de trop de colonnes, grands ensembles de résultats, ou paramètres mémoire tmp insuffisants.
Correction : Indexer pour satisfaire ORDER BY/GROUP BY, réduire les colonnes sélectionnées, pré-agréger ou refactoriser. Augmenter la mémoire seulement après avoir arrêté l’hémorragie.
4) Le « lag de réplication qui se déguise en lenteur applicative »
Signature : Les lectures routées vers les replicas deviennent plus lentes ou obsolètes ; ou les basculements deviennent risqués car le lag est élevé.
Causes : Une grosse transaction, pression de commit, contraintes de dépendance, ou un replica ayant un profil I/O/cache différent du primaire.
Correction : Fractionnez les gros writes, ajustez le parallélisme de réplication de manière appropriée, et assurez-vous que les replicas ont des performances de stockage comparables (ou ajustez le routage).
5) Le « plan a changé et personne ne l’a remarqué »
Signature : Même empreinte de requête, soudainement pire. EXPLAIN montre un index ou un ordre de jointure différent de la semaine dernière.
Causes : Les statistiques ont changé, la distribution des données a évolué, ou une mise à niveau de version a modifié le comportement de l’optimiseur.
Correction : Rafraîchir les stats de manière intentionnelle, envisager des histogrammes, stabiliser avec des hints seulement si nécessaire, et surveiller la dérive de plan pour les requêtes critiques.
Blague #2 : L’optimiseur est comme un chat : parfois brillant, parfois en train de dormir sur le clavier, et il ne s’expliquera pas.
Trois mini-récits d’entreprise depuis le terrain
Mini-récit 1 : L’incident causé par une mauvaise hypothèse
L’entreprise avait une séparation claire : writes sur le primaire, reads sur les replicas. L’hypothèse était simple et rassurante : « Le trafic de lecture ne peut pas nuire aux writes. » Tout le monde la répétait jusqu’à ce qu’elle devienne une politique.
Puis le flux de checkout a commencé à expirer. Le CPU du primaire n’était pas saturé, mais la latence de commit augmentait. Les replicas allaient bien. Le réseau allait bien. L’équipe applicative jurait que c’était « juste quelques lectures supplémentaires ».
Performance Schema a montré la vérité : un nouveau endpoint « read-only » faisait une jointure multi-table avec un prédicat manquant, scannant de larges plages. Il s’exécutait sur le primaire en fallback quand le pool de lectures était épuisé. La requête elle-même n’était même pas très lente par exécution — juste assez fréquente pour évincer les pages chaudes et provoquer de la churn dans le buffer pool.
L’hypothèse erronée ne portait pas sur les « lectures ». Elle portait sur l’endroit où les lectures ont lieu en cas de panne partielle. L’épuisement du pool de connexions, le lag des replicas, des flaps DNS — n’importe lequel de ces éléments peut router des lectures vers le primaire. La requête est devenue un problème du chemin d’écriture en détruisant la localité du cache et en augmentant les E/S.
Ils ont corrigé cela en ajoutant le prédicat manquant et un index, puis en implémentant un circuit breaker : si les replicas sont indisponibles, dégrader la fonctionnalité plutôt que frapper discrètement le primaire. Le monitoring a été mis à jour pour alerter sur les « empreintes de requêtes de lecture sur le primaire » comme risque de première classe.
Mini-récit 2 : L’optimisation qui a mal tourné
Une équipe voulait réduire la latence p99 d’un endpoint de recherche. Ils ont ajouté un index qui semblait évidemment correct, et cela a amélioré leur requête de test. Déploiement, high fives.
Deux jours plus tard, la charge d’écriture a commencé à stagner par à-coups. Le CPU de la base a augmenté, puis diminué, mais le débit est devenu irrégulier. Le slow query log n’affichait pas une seule « mauvaise requête ». C’était l’indice : la douleur n’était pas dans le temps d’exécution des requêtes ; c’était dans l’amplification des writes.
Le nouvel index a multiplié le coût des updates. Un job en arrière-plan qui touchait beaucoup de lignes devait maintenant maintenir une structure supplémentaire avec une mauvaise localité. Le buffer pool a été chamboulé par les pages d’index. Performance Schema a montré une augmentation de la contention sur les mutex autour des structures du buffer pool, et iostat a montré une latence d’écriture élevée pendant les pics.
La correction n’était pas « supprimer les index ». C’était : rendre le job moins destructeur (batching, moins de concurrence) et repenser l’index pour correspondre aux prédicats réels. Ils ont remplacé un index large par un index composite plus étroit qui satisfaisait le chemin de lecture sans pénaliser autant les updates.
Leçon : « optimiser » change les compromis. Si vous ne mesurez pas le chemin d’écriture, vous ne faites pas de l’optimisation — vous jouez à la roulette.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une autre organisation avait un rituel dont personne ne se vantait : ils gardaient le slow query logging activé à un seuil bas, faisaient une rotation correcte des logs et digéraient les résultats chaque semaine. Pas d’héroïsme, juste de l’hygiène.
Un vendredi après-midi, la p95 a bondi. L’on-call a ouvert PMM Query Analytics et a vu une nouvelle empreinte en tête en moins d’une minute. C’était une requête ajoutée dans une release mineure : une recherche par colonne token, mais le token était stocké dans un type différent du paramètre, déclenchant une conversion implicite et tuant l’utilisation d’index.
Parce que les slow logs et les digests étaient habituels, ils avaient des baselines. Ils ont pu prouver « cette requête n’existait pas hier » et la lier à un déploiement. Le rollback a été immédiat. Puis l’équipe a ajouté l’index correct et aligné les types dans le patch suivant.
Pas de salle de crise. Pas de réunion générale. La correction a été presque ennuyeuse. C’est le but : l’observabilité ennuyeuse prévient les outages excitants.
Paraphrase d’une idée de Gene Kranz : fermes et compétents
— restez calmes, suivez la procédure et utilisez les données plutôt que les impressions.
Erreurs courantes : symptôme → cause racine → correction
1) Symptom: CPU is high, but slow query log is quiet
Cause racine : Beaucoup de requêtes moyennement lentes sous le seuil, ou overhead dans le parsing/exécution non capturé à cause d’un long_query_time élevé.
Correction : Baissez temporairement long_query_time à 200–500ms, ou activez l’agrégation digest de Performance Schema et classez par temps total.
2) Symptom: Threads connected is high, Threads running is low, latency is terrible
Cause racine : Attentes de verrous ou d’autres ressources (E/S, verrous metadata). Tout le monde est en file d’attente.
Correction : Utilisez les vues sys pour les lock waits et MDL waits ; identifiez le bloqueur ; réduisez la longueur des transactions ; planifiez correctement les DDL.
3) Symptom: Queries “randomly” get slow after restart
Cause racine : Buffer pool froid ; working set non chauffé ; lectures disque qui explosent.
Correction : Attendez-vous à cela et planifiez-le (stratégies de warmup, buffer pool suffisant, marge de capacité). Vérifiez aussi que vous n’avez pas perdu la localité du cache de requêtes.
4) Symptom: Replica lag spikes during a batch job
Cause racine : Grosses transactions ou taux de commit élevé saturent la capacité d’application/flush du replica, ou les contraintes de dépendance limitent le parallélisme.
Correction : Fractionnez les writes en transactions plus petites, ajustez la concurrence du job et assurez-vous que la durabilité et le stockage du replica correspondent à l’intention.
5) Symptom: “Waiting for table metadata lock” appears during deploys
Cause racine : DDL en collision avec des requêtes/transactions longues ; MDL est détenu plus longtemps qu’attendu.
Correction : Éliminez les longues transactions, exécutez les changements de schéma avec des outils en ligne et faites respecter des fenêtres DDL ou un throttling.
6) Symptom: p99 worsens, averages look fine
Cause racine : Contention, mise en file d’attente, skew de paramètres ou changements intermittents de plan. La moyenne ment.
Correction : Utilisez les percentiles dans QAN/monitoring, vérifiez les statements par latence max et par 95e percentile, et inspectez des exemples de requêtes spécifiques.
7) Symptom: Disk utilization isn’t 100%, but latency is high
Cause racine : La profondeur de file et la latence peuvent augmenter bien avant que %util atteigne 100 % (surtout sur du stockage partagé). Les petites E/S aléatoires sont les tueurs silencieux.
Correction : Faites confiance à await et à la profondeur de file. Réduisez l’amplification de lecture (index, forme de requête), réduisez les déversements temporaires et vérifiez la santé du stockage.
8) Symptom: You “fixed” a query with an index, but overall throughput dropped
Cause racine : Overhead de maintenance des index sur les écritures, surtout pour les tables à fort churn.
Correction : Réévaluez la conception des index, envisagez des index composites/narrow, réduisez le churn d’écriture et benchmarquez les chemins de lecture et d’écriture.
Listes de contrôle / plan étape par étape
Checklist A: Baseline monitoring you should have before the next incident
- Activez le Performance Schema et gardez-le activé.
- Activez le slow query log avec un seuil qui capture les douleurs réelles (souvent 200–500ms, ajustez selon la charge).
- Faites la rotation et conservez les slow logs ; assurez-vous que le disque ne se remplira pas.
- Exécutez un rapport digest quotidien/hebdomadaire et suivez les empreintes en tête au fil du temps.
- Collectez les métriques d’événements d’attente et ayez au moins un tableau de bord qui les classe.
- Suivez le lag de réplication et alertez sur la tendance, pas seulement sur la valeur absolue.
- Sauvegardez des exemples de requêtes pour les principaux digests (PMM aide, mais vous pouvez stocker des échantillons vous-même).
Checklist B: Incident response steps (15-minute version)
- Classer : CPU vs E/S vs verrous vs réplication.
- Récupérer les digests principaux par temps total et par temps moyen.
- Vérifier les sessions actives et les lock waits ; trouver les bloqueurs.
- Si E/S : vérifier les tables temporaires sur disque et les scans (rows examined).
- Valider le plan pour l’empreinte principale avec EXPLAIN.
- Choisir une action : kill une requête bloquante, ajouter un index (seulement si sûr et testé), throttler un job ou rollbacker un déploiement.
- Mesurer à nouveau. Si la métrique n’a pas bougé, votre correction n’était pas la bonne.
Checklist C: Hardening changes that reduce future “killer query” risk
- Ajoutez des garde-fous dans l’app : timeouts, circuit breakers et limites de concurrence des requêtes.
- Privilégiez les prepared statements et des formes de requêtes stables (aide le digesting et réduit le chaos de plan).
- Standardisez la pratique des changements de schéma (approche online schema change, conscience MDL).
- Faites des « rows examined per call » un SLO suivi pour les endpoints critiques.
- Rédigez un plan de rollback pour chaque changement affectant les requêtes (les index comptent).
FAQ
1) Is Percona Server “better” than MySQL for monitoring?
Il est meilleur comme une bonne boîte à outils est meilleure qu’une seule clé. MySQL fournit les primitives ; l’écosystème Percona tend à les rendre opérationnellement utilisables plus rapidement, surtout avec PMM et la culture tooling.
2) Should I rely on slow query log or Performance Schema?
Utilisez les deux. Le slow log vous donne les statements exacts et les timings dans un fichier durable. Performance Schema vous donne la vérité agrégée en temps réel et le contexte d’attente. Quand ils s’accordent, vous pouvez agir vite.
3) What long_query_time should I use?
Commencez autour de 200–500ms pour les services OLTP et ajustez selon le volume et le stockage. Si votre cible p95 est 50ms, un seuil à 10s est de la négligence d’observation.
4) Does enabling Performance Schema hurt performance?
Ça peut si vous activez tous les instruments et consommateurs. Avec des valeurs par défaut raisonnables et une instrumentation sélective, c’est généralement acceptable en production. Mesurez l’overhead en staging si vous êtes anxieux ; ne le désactivez pas par superstition.
5) How do I find “killer queries” when everything is fast individually?
Classez par temps total et nombre d’appels, pas seulement par latence moyenne. Une requête à 5ms exécutée 50 000 fois par minute peut consommer tout votre budget CPU.
6) What’s the fastest way to diagnose lock issues?
Utilisez les vues sys : sys.innodb_lock_waits et sys.schema_table_lock_waits. Trouvez le PID bloqueur, regardez sa requête et l’âge de sa transaction, et décidez de kill ou de corriger le pattern de charge.
7) Why do I see replication lag but the primary looks healthy?
L’application sur le replica est une charge différente : cache différent, I/O différent, parfois mix de requêtes différent (charge de lecture). Le lag est souvent causé par une grosse transaction ou une pression de commit, pas par une détresse du primaire.
8) Can PMM replace my existing monitoring?
Il complète votre monitoring. Vous avez toujours besoin des métriques hôtes, des logs et d’une discipline d’alerting. PMM brille pour l’analyse des requêtes et la visibilité spécifique à la base, ce qui manque souvent lors des incidents.
9) Should I kill “killer queries” during incidents?
Parfois. Si une requête bloque les autres ou provoque un convoi de verrous, la tuer peut restaurer le service rapidement. Mais prenez la décision explicitement : kill query vs kill connection, et comprenez les bouffées de retry de l’application.
10) What if the “killer query” is actually many similar queries?
C’est exactement pour cela que les digests existent. Corrigez le pattern : ajoutez le bon index, changez la forme de la requête ou ajoutez du caching. Ne tapez pas les statements individuels comme des taupes.
Conclusion : étapes concrètes à réaliser cette semaine
Si vous voulez arrêter de deviner, votre objectif est simple : faites en sorte que la base de données s’explique rapidement.
- Activez le slow logging avec un seuil réaliste et faites-en la rotation.
- Gardez Performance Schema activé, et utilisez les vues du schéma sys pour éviter d’écrire du SQL archéologique.
- Adoptez un workflow de digesting : classement quotidien/hebdomadaire, suivi des empreintes en tête, et traitez le « rows examined per call » comme un mauvais signe.
- Choisissez un seul tableau de bord « diagnostic rapide » : digests en tête, top waits, sessions actives, lag de réplication. Si ce n’est pas visible, ça n’existe pas pendant un incident.
- Décidez de votre position sur Percona : si vous avez besoin d’une visibilité opérationnelle plus rapide avec moins de colle maison, Percona Server + PMM est un choix pragmatique. Si vous êtes engagé sur MySQL upstream, appliquez la même discipline avec vos propres outils.
La plupart des requêtes tueuses ne sont pas ingénieuses. Ce sont des requêtes ordinaires placées dans des conditions extraordinaires. Votre travail est de rendre ces conditions visibles — et de rendre le coupable douloureusement facile à nommer.