MariaDB vs MySQL : la checklist unique qui trouve les goulots d’étranglement plus vite que les réglages

Cet article vous a aidé ?

La plupart des « optimisations de performance » pour bases de données commencent au mauvais endroit : un fichier de configuration. Quelqu’un se rappelle d’un billet de blog, tourne un potentiomètre, redémarre la production et espère que les graphiques se taisent. Parfois ça marche. Souvent ça ne fait que déplacer la douleur vers un autre sous-système et allonger l’autopsie.

La façon la plus rapide de trancher les débats MariaDB vs MySQL est ennuyeuse : identifier le goulot d’étranglement avant de toucher quoi que ce soit. Cette checklist fait ça. C’est la même démarche que j’utilise quand une voie de revenus subit des timeouts et que la seule « repro » est « c’est lent parfois. »

La checklist unique : localiser le goulot d’étranglement, puis optimiser

MariaDB et MySQL partagent beaucoup d’ADN, surtout si vous vivez principalement dans l’univers InnoDB. C’est pourquoi « lequel est plus rapide » est généralement la mauvaise question. La bonne question est : qu’est-ce qui limite actuellement le débit ou la latence sur cet hôte ? CPU. E/S. Verrous. Mémoire. Réseau. Réplication. Ou le classique : une requête pathologique qui entraîne tout le reste dans la boue.

Voici le modèle mental qui vous garde honnête :

  • Goulot de débit : vous exploitez au maximum une ressource (CPU, bande passante disque, mutex). Symptômes : le QPS plafonne tandis que la charge augmente ; la latence peut ou non monter.
  • Goulot de latence : quelque chose bloque occasionnellement (pics de fsync, verrous, misses du buffer pool, blocage dans l’application de la réplication). Symptômes : la latence en queue augmente ; « c’est lent parfois. »
  • Goulot de mise en file : une étape mono‑thread ou sérialisée (flush du redo log, thread SQL de réplication, verrouillage métadonnées). Symptômes : une file s’allonge ; tout le monde attend son tour.

Quand vous êtes sous pression, les réglages semblent productifs parce qu’ils sont concrets. Mais les réglages sont des décisions en aval. Si vous ne connaissez pas le goulot, vous changez essentiellement des variables au hasard et déclarez victoire quand l’incident suivant se produit un autre jour.

Règle opérationnelle : n’optimisez pas tant que vous ne pouvez pas dire, en une phrase, « le goulot est X, prouvé par Y. » Si vous ne le pouvez pas, vous n’optimisez pas ; vous jouez.

Et oui, vous optimiserez quand même. Mais vous optimiserez avec des preuves.

Petite blague #1 : « Nous avons optimisé MySQL jusqu’à ce qu’il soit rapide. » C’est adorable. Le load balancer s’en souvient.

Faits et historiques qui comptent encore en production

Un peu de contexte aide parce que ça explique pourquoi les valeurs par défaut diffèrent, pourquoi certains comportements vous surprennent, et pourquoi le « conseil MySQL » de quelqu’un casse sur MariaDB (ou vice‑versa).

  1. MariaDB a été forké de MySQL en 2009 après des inquiétudes liées au rachat de MySQL par Oracle. Ce n’est pas de la trivia : c’est pourquoi les hypothèses de l’écosystème dérivent au fil du temps.
  2. Au départ, MariaDB a été commercialisée comme « remplaçant drop-in » de façon agressive. Opérationnellement, c’est en grande partie vrai au niveau SQL — mais le comportement de performance peut diverger sur les bords (optimiseur, pool de threads, fonctionnalités de réplication).
  3. MySQL 5.6/5.7 a fait d’InnoDB le centre de gravité et a considérablement amélioré performance_schema et l’instrumentation. Si vous le traitez encore comme une boîte noire, vous laissez de l’autodiagnostic gratuit sur la table.
  4. MySQL 8.0 a supprimé le query cache (notoire pour la contention sur les mutex). Si votre parc MariaDB utilise encore query cache, c’est un artefact historique avec des conséquences modernes.
  5. MariaDB a divergé avec des fonctionnalités comme Aria, ColumnStore et l’intégration Galera (dans de nombreuses distributions). Ce ne sont pas que des fonctionnalités ; elles modifient les modes de défaillance et les réglages opérationnels.
  6. La refonte du dictionnaire de données dans MySQL 8.0 a changé les métadonnées et certaines réalités d’upgrade/rollback. « Ça marchait sur 5.7 » n’est pas un plan.
  7. Les deux s’appuient fortement sur le comportement IO de Linux (fsync, vidage des pages « dirty », ordonnanceur). Nombre d’incidents « base de données » ne sont que le noyau qui fait ce que vous avez demandé, pas ce que vous vouliez.
  8. La réplication a évolué différemment : MySQL a mis l’accent sur les GTID, la réplication multi‑thread, group replication ; MariaDB a sa propre implémentation de GTID et souvent des ergonomies de réplication différentes. Même mot, différents pièges.

Also : les conversations sur la performance vieillissent mal. Un guide d’optimisation écrit pour MySQL 5.5 sur disques spinning peut être activement nuisible sur MySQL 8.0 sur NVMe avec cgroups et voisins bruyants.

Exigence d’une citation, idée paraphrasée : l’espoir n’est pas une stratégie. — souvent attribuée dans les opérations ; utilisez‑la comme principe prêt au pager, pas comme slogan.

Playbook de diagnostic rapide (premier/deuxième/troisième)

Ceci est l’ordre « entrer dans une pièce en feu ». Conçu pour la rapidité, pas pour l’élégance. L’objectif est d’identifier si vous êtes lié par le CPU, les E/S, les verrous ou la réplication en quelques minutes.

Premier : prouver quel type de goulot vous avez

  • L’hôte est‑il saturé en ressources ? CPU, pression mémoire, IO wait, latence disque, erreurs réseau.
  • La base de données attend‑elle ? Attentes de verrous, fsync/redo, misses du buffer pool, ordonnancement des threads, tempêtes de connexions.
  • La base de données fait‑elle quelque chose de coûteux ? Scans complets, mauvais plans, tris sur disque, tables temporaires, index manquants.

Deuxième : identifier le coupable principal, pas la moyenne

  • Requêtes les plus coûteuses par temps total (pas par nombre).
  • Attentes principales (verrous, E/S, flush, verrous métadonnées).
  • Tables les plus actives (lignes chaudes, index manquants, forte rotation).

Troisième : décider atténuer, corriger ou monter en capacité

  • Atténuer maintenant : tuer les requêtes incontrôlées, réduire la charge, augmenter prudemment les tailles de pool, réduire la concurrence, déplacer les lectures vers des réplicas.
  • Corriger ensuite : modifications d’index, réécriture de requêtes, changements de schéma, partitionnement, correction des niveaux d’isolation.
  • Monter en capacité quand approprié : plus de CPU pour les charges liées au CPU, stockage plus rapide pour les charges IO, réplicas pour la diffusion de lectures, sharding seulement si vous vous aimez souffrir (ou si c’est vraiment nécessaire).

Astuce vitesse : ne « samplez » pas en regardant les moyennes. Cherchez les files et la latence en queue. Les moyennes sont ce que vous dites à la direction. Les files sont ce que vous dites à l’astreinte.

Tâches pratiques (commandes, sorties, décisions)

Ce sont des tâches exécutables sur un hôte Linux DB et dans la base. Chacune inclut (a) une commande, (b) ce que la sortie signifie, et (c) la décision à prendre. Cette dernière partie fait la différence entre monitoring et opérations.

Tâche 1 : confirmer quel moteur et quelle version vous exécutez réellement

cr0x@server:~$ mysql -e "SELECT VERSION() AS version, @@version_comment AS comment, @@version_compile_machine AS arch;"
+------------------------+------------------------------+------+
| version                | comment                      | arch |
+------------------------+------------------------------+------+
| 10.6.16-MariaDB-1:...  | MariaDB Server               | x86_64 |
+------------------------+------------------------------+------+

Signification : Ne déboguez pas « MySQL » quand vous exécutez MariaDB (ou vice‑versa). Les différences de version déterminent l’instrumentation disponible et le comportement de l’optimiseur.

Décision : Choisissez les bons outils : les workflows lourds basés sur performance_schema sont plus matures dans MySQL 8.0 ; MariaDB peut nécessiter des compteurs d’état différents et des équivalents du schéma sys.

Tâche 2 : contrôle rapide de la pression sur l’hôte (CPU, IO wait, run queue)

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  1      0  81232  64312 987654    0    0   120   980  900 2100 25 10 45 20  0
 8  2      0  79012  64320 987120    0    0   110  1600 1100 3200 35 12 33 20  0

Signification : Un r élevé suggère une contention CPU ; un wa élevé suggère des attentes sur les E/S ; un b soutenu indique des tâches bloquées.

Décision : Si wa est constamment élevé et que la latence est en pics, allez directement aux tâches de latence disque. Si r est élevé avec wa faible, concentrez‑vous sur le CPU des requêtes et la contention.

Tâche 3 : mesurer la latence disque, pas seulement le débit

cr0x@server:~$ iostat -x 1 3
Device            r/s   w/s  rkB/s  wkB/s  await  svctm  %util
nvme0n1          1200  9000  48000 320000   18.5   0.3   92.0

Signification : await est le temps que passent les requêtes en attente + en service. Un await élevé avec une forte utilisation signifie que le device est saturé ou que quelque chose en amont met en file.

Décision : Si les pics d’await coïncident avec la latence DB, vous êtes IO‑bound. Atténuez en réduisant les rafales d’écriture (checkpoint/flush), en améliorant le buffer pool, ou en passant à un stockage plus rapide. Si await est faible mais que la DB est lente, le goulot est ailleurs.

Tâche 4 : trouver les threads MySQL/MariaDB les plus actifs au niveau OS

cr0x@server:~$ top -H -p $(pidof mysqld)
top - 12:01:22 up 34 days,  2 users,  load average: 9.10, 8.80, 7.90
Threads:  210 total,   8 running, 202 sleeping
%Cpu(s): 70.0 us, 10.0 sy,  0.0 ni, 20.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
12345 mysql     20   0 12.3g  8.1g  0.0g R  180.0 52.0  12:11.04 mysqld

Signification : Si vous voyez quelques threads saturés, vous pouvez avoir des requêtes gourmandes en CPU ou de la contention sur des mutex. Si le CPU est bas mais la latence élevée, concentrez‑vous sur les attentes/verrous/E/S.

Décision : Si le CPU est saturé, passez au profilage des requêtes et aux plans. Sinon, regardez les attentes de verrou et d’E/S à l’intérieur d’InnoDB.

Tâche 5 : identifier immédiatement les requêtes incontrôlées

cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST\G" | sed -n '1,80p'
*************************** 1. row ***************************
     Id: 123
   User: app
   Host: 10.0.2.14:51234
     db: prod
Command: Query
   Time: 88
  State: Sending data
   Info: SELECT ... FROM orders JOIN order_items ...

Signification : Time est la durée d’exécution. State indique ce qu’elle fait (pas parfaitement). Une masse de « Sending data » de longue durée signifie souvent de larges scans/joints.

Décision : Si une requête unique domine et impacte les utilisateurs, envisagez de la tuer et de limiter l’appelant. Capturez ensuite la requête pour EXPLAIN et travail d’indexation.

Tâche 6 : détecter rapidement la contention de verrous (InnoDB)

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,140p'
TRANSACTIONS
------------
Trx id counter 987654321
Purge done for trx's n:o < 987654000 undo n:o < 0 state: running
History list length 12045
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 987654310, ACTIVE 92 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 123, OS thread handle 1400, query id 7777 10.0.2.14 app updating
UPDATE accounts SET balance=balance-? WHERE id=?

Signification : Des transactions « ACTIVE » de longue durée et un History list length élevé se corrèlent souvent avec du purge lag, la croissance des undo, et des symptômes secondaires comme des « random slow queries ».

Décision : Si vous voyez une transaction de longue durée tenant des verrous, vous devrez peut‑être la tuer, corriger le périmètre transactionnel de l’application, ou baisser l’isolation quand c’est sûr. Enquêtez aussi pourquoi le purge n’arrive pas à suivre (souvent à cause de lecteurs longs).

Tâche 7 : vérifier la pression sur le buffer pool et les indicateurs de hit ratio

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| Innodb_buffer_pool_reads| 124009876 |
+-------------------------+-----------+
+----------------------------------+-------------+
| Variable_name                    | Value       |
+----------------------------------+-------------+
| Innodb_buffer_pool_read_requests | 98012345678 |
+----------------------------------+-------------+

Signification : Innodb_buffer_pool_reads sont des lectures physiques. Si ce compteur augmente rapidement par rapport aux requêtes de lecture, vous manquez le cache et payez la latence disque.

Décision : Si les lectures physiques sont élevées et que la latence disque est notable, augmentez le buffer pool (si la RAM le permet) ou réduisez l’empreinte de travail (indexes, patterns de requêtes). Si le buffer pool est correct, ne le touchez pas.

Tâche 8 : détecter les débordements de tables temporaires (tris/joints sur disque)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Created_tmp_disk_tables | 9123456  |
| Created_tmp_tables      | 12345678 |
+-------------------------+----------+

Signification : Un ratio élevé de tables temporaires sur disque suggère des limites mémoire trop basses pour les tris/joints ou des requêtes intrinsèquement sujettes aux débordements (index manquants, gros GROUP BY).

Décision : Si les temp sur disque se corrèlent avec des endpoints lents, corrigez d’abord les plans de requête (index), puis ajustez prudemment les limites de mémoire pour les tables temporaires. Des limites plus grandes peuvent aussi provoquer des explosions mémoires sous forte concurrence.

Tâche 9 : vérifier la pression sur le redo log et les flushs (douleur fsync)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs'; SHOW GLOBAL STATUS LIKE 'Innodb_log_waits';"
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| Innodb_os_log_fsyncs | 77881234 |
+----------------------+----------+
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_log_waits | 12345 |
+------------------+-------+

Signification : Innodb_log_waits indique des attentes pour l’espace du log buffer / flush. Combiné à un taux élevé de fsync et une latence E/S, cela pointe vers des goulots d’écriture.

Décision : Si les log waits augmentent, vous atteignez un plafond de commits/flushs. Atténuez avec un stockage plus rapide, réduisez le taux de transaction, regroupez les écritures, ou ajustez la durabilité seulement avec un accord métier explicite.

Tâche 10 : vérifier le retard de réplication et s’il vient des E/S, de l’application SQL ou des verrous

cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep -i "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running|Relay_Log_Space"
Seconds_Behind_Master: 420
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Relay_Log_Space: 987654321

Signification : Le thread IO est OK, le thread SQL tourne, mais le retard est élevé et les relay logs s’accumulent. Le réplica peut récupérer mais ne peut pas appliquer assez vite.

Décision : Enquêtez sur les goulots d’application : application mono‑thread, lignes chaudes, DDL, transactions longues ou stockage lent. Considérez la réplication multi‑thread où c’est supporté et sûr, et corrigez les patterns de requêtes/transactions qui sérialisent l’application.

Tâche 11 : vérifier les tempêtes de connexions et l’ordonnancement des threads

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads%'; SHOW GLOBAL STATUS LIKE 'Connections';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 980   |
| Threads_running   | 64    |
| Threads_cached    | 0     |
+-------------------+-------+
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Connections   | 987654 |
+---------------+--------+

Signification : Beaucoup de threads connectés avec un cache faible peuvent signifier du churn de connexion (apps sans pool) ou un thread_cache trop bas.

Décision : Corrigez l’application d’abord : activez le pooling, réutilisez les connexions, limitez la concurrence. Ensuite seulement ajustez le thread cache / les pools ; sinon l’application continuera à vous nuire plus rapidement.

Tâche 12 : vérifier la taille table/index vs mémoire pour prévoir les misses de cache

cr0x@server:~$ mysql -e "SELECT table_schema, SUM(data_length+index_length)/1024/1024/1024 AS gb FROM information_schema.tables GROUP BY table_schema ORDER BY gb DESC;"
+--------------+--------+
| table_schema | gb     |
+--------------+--------+
| prod         | 820.12 |
| mysql        | 0.05   |
+--------------+--------+

Signification : Si votre dataset actif fait des centaines de Go et que votre buffer pool est à 32 Go, vous ne vous en sortirez pas avec du cache. Les lectures seront des lectures disque. Les plans doivent être sélectifs.

Décision : Soit allouez suffisamment de RAM pour la working set, soit concevez des requêtes/index pour éviter les scans. Si aucune option n’est possible, déléguez les lectures aux réplicas ou acceptez que la latence en queue soit façonnée par le stockage.

Tâche 13 : attraper les accumulations de verrous métadonnées (DDL et ALTER « inoffensifs »)

cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;" | awk '$6 ~ /Waiting/ || $7 ~ /metadata/ {print}'
234 app 10.0.2.21:49910 prod Query 35 Waiting for table metadata lock ALTER TABLE users ADD COLUMN ...

Signification : Les verrous métadonnées bloquent les requêtes qui touchent la table. Un seul DDL peut geler un chemin de table chaud s’il attend derrière une transaction longue.

Décision : Suspendez le DDL, trouvez et terminez la transaction longue, ou utilisez des techniques de changement de schéma en ligne. Aussi : planifiez les modifications de schéma comme vous planifiez un risque, pas comme vous planifiez le déjeuner.

Tâche 14 : inspecter rapidement le slow query log (si activé)

cr0x@server:~$ sudo tail -n 20 /var/log/mysql/slow.log
# Time: 2025-12-31T11:55:22.123456Z
# Query_time: 12.400  Lock_time: 0.000 Rows_sent: 50  Rows_examined: 50000000
SELECT ... FROM events WHERE customer_id=... ORDER BY created_at DESC LIMIT 50;

Signification : Les lignes examinées sont révélatrices. 50M examiné pour renvoyer 50 lignes est un problème d’index ou de plan, pas un problème « augmenter le buffer pool ».

Décision : Ajoutez/réparez l’index (probablement (customer_id, created_at)), validez avec EXPLAIN, et envisagez une réécriture de la requête. Ne « tunez » pas autour d’un index manquant.

Tâche 15 : valider le plan avec EXPLAIN et le rendre lisible

cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM events WHERE customer_id=42 ORDER BY created_at DESC LIMIT 50\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: events
         type: ALL
possible_keys: idx_customer_created
          key: NULL
      rows: 50000000
     Extra: Using where; Using filesort

Signification : type: ALL et filesort sur une grande table indiquent un scan complet + tri. L’index existe mais n’est pas utilisé (ordre incorrect, faible sélectivité, collation différente, fonction sur la colonne, ou statistiques obsolètes).

Décision : Corrigez la requête ou l’index. Si les stats sont obsolètes, lancez analyze. Si l’optimiseur se trompe, envisagez des hints seulement en dernier recours et seulement avec des tests de régression.

Tâche 16 : vérifier les deadlocks (ils ne sont pas rares ; ils sont juste sous‑rapportés)

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,+60p'
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 987654400, ACTIVE 2 sec updating or deleting
...
WE ROLL BACK TRANSACTION (1)

Signification : Les deadlocks sont des bugs de concurrence, pas des « bugs de base de données ». Ils sont généralement causés par un ordre d’update incohérent ou des transactions trop volumineuses.

Décision : Corrigez l’ordre des opérations et le périmètre transactionnel de l’application. Puis assurez‑vous que la logique de retry est correcte et bornée. Réessayer indéfiniment, c’est de la cosmétique de deadlock.

Tâche 17 : mesurer le système de fichiers et les options de montage qui affectent fsync

cr0x@server:~$ mount | egrep ' /var/lib/mysql | /data '
/dev/nvme0n1p2 on /var/lib/mysql type ext4 (rw,noatime,data=ordered)

Signification : Le système de fichiers et les options de montage influent sur les mises à jour métadonnées et les barrières d’écriture. Vous n’avez pas besoin de réglages exotiques ; vous avez besoin de réglages cohérents et testés.

Décision : Si vous observez des blocages fsync, validez le firmware du stockage, l’intégrité du système de fichiers et les logs du noyau avant de toucher aux paramètres de durabilité de la base de données.

Tâche 18 : vérifier que la base de données n’échange pas sur le swap (ça ruine tout silencieusement)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi        28Gi       0.8Gi       0.2Gi        2.2Gi       1.1Gi
Swap:            4Gi        1.9Gi       2.1Gi

Signification : Si le swap est utilisé sur un hôte DB, la latence devient imprévisible. Même « un peu de swap » peut signifier que des pages importantes sont évincées.

Décision : Réduisez l’empreinte mémoire (buffer pool trop grand, buffers par connexion trop élevés), ajoutez de la RAM, ou serrez la concurrence. Ne normalisez pas le swap sur des bases de données à moins d’aimer les romans policiers.

Checklists / plan pas-à-pas

Voici la checklist opérationnelle que je veux voir au mur (ou dans le runbook). C’est l’ensemble minimal d’étapes qui vous mène systématiquement à une réponse sans optimiser selon le folklore.

Checklist A : triage en 10 minutes (la production est en feu)

  1. Confirmer le périmètre : un endpoint, un tenant ou tout ? Récupérez une fenêtre temporelle et les taux d’erreur depuis votre monitoring applicatif.
  2. Saturation hôte : exécutez vmstat et iostat -x. Décidez : CPU‑bound ou IO‑bound.
  3. File d’attente DB : SHOW FULL PROCESSLIST pour voir s’il y a beaucoup d’états « Waiting for … » ou quelques longs runners.
  4. Verrous : SHOW ENGINE INNODB STATUS et cherchez transactions longues, attentes de verrous, deadlocks.
  5. Réplication (si pertinente) : vérifiez le lag. Si les lectures sont servies par des réplicas, le lag est un bug visible par l’utilisateur.
  6. Atténuation immédiate : tuez la pire requête, réduisez la charge, suspendez le DDL, ou basculez temporairement le trafic de lecture.
  7. Capturer des preuves : extrait du slow log, EXPLAIN, compteurs d’état pertinents avant de redémarrer quoi que ce soit.

Checklist B : diagnostic en 1 heure (trouver le vrai goulot)

  1. Top requêtes par temps total : utilisez l’agrégation du slow log ou les vues performance schema (si disponibles). Concentrez‑vous sur le temps total, pas seulement la plus longue requête.
  2. Validation des plans : EXPLAIN des coupables principaux. Cherchez scans complets, filesort, tables temporaires, mauvais ordre de jointure.
  3. Classification des attentes : identifiez si les attentes sont principalement IO, verrous ou CPU. Si MySQL 8.0, performance_schema peut montrer cela clairement ; sur MariaDB, fiez‑vous davantage à InnoDB status + compteurs globaux.
  4. Vérification de la forme des données : tailles de tables, tailles d’indexes, distributions biaisées, lignes chaudes.
  5. Vérification de la concurrence : threads en cours, connexions, périmètre des transactions, tailles des batches.
  6. Vérification du stockage : logs noyau, latence disque, comportement du système de fichiers, pics de writeback.
  7. Décider la classe de correction : requête/index, comportement applicatif, stockage, ou capacité.

Checklist C : séquence sûre d’optimisation (seulement après identification du goulot)

  1. Corrections requête/index d’abord (ROI le plus élevé, risque systémique le plus faible). Rendre testable et réversible.
  2. Contrôles de concurrence ensuite (pooling, mise en file, limites). Empêcher les tempêtes.
  3. Dimensionnement mémoire ensuite (buffer pool, buffers par connexion). Surveillez swap et OOM.
  4. Réglages IO/durabilité en dernier et seulement avec acceptation explicite du risque. Vous pouvez acheter de la performance en sacrifiant la durabilité ; vous ne pouvez pas racheter la durabilité après une perte de données.

Petite blague #2 : Désactiver la durabilité, c’est comme enlever le détecteur de fumée parce qu’il est bruyant. Vous dormirez très bien… jusqu’au jour où vous ne dormirez plus du tout.

Trois mini-récits du monde de l’entreprise (anonymisés, réalistes)

Mini-récit 1 : l’incident causé par une mauvaise hypothèse

Une entreprise SaaS de taille moyenne a migré un service critique de MySQL vers MariaDB parce que le paquet fournisseur le rendait simple et que l’application « n’utilise que du SQL standard ». L’équipe a effectué une migration de schéma soignée, exécuté des tests d’intégration et déclaré le succès. La production était une autre bête.

L’hypothèse erronée était subtile : ils ont supposé que leur charge était liée aux requêtes. Ce n’était pas le cas. En production, ils exécutaient une charge d’écriture à haute concurrence avec des pics périodiques — événements de facturation de fin d’heure — chaque transaction touchant quelques lignes chaudes. L’application utilisait des transactions longues avec des lectures supplémentaires « par sécurité ». Ces lectures n’étaient pas sans conséquence. Elles tenaient des verrous et augmentaient la pression de purge.

En moins d’un jour, des « requêtes lentes aléatoires » sont apparues. Puis le lag de réplication a grimpé. Puis un réplica a été promu pendant une fenêtre de maintenance séparée et le système s’est écroulé : le nouveau primaire avait une grande history list length, le stockage subissait des stalls fsync périodiques, et l’application amplifiait le problème en ouvrant de nouvelles connexions lors des retries.

Ils ont d’abord accusé le choix du moteur. C’était commode et faux. Le véritable coupable était le périmètre des transactions et la contention sur les lignes chaudes. Quand ils ont raccourci les transactions, ajouté un index composite manquant, et introduit des retries bornés, MariaDB comme MySQL se comportaient bien. Le moteur importait moins que la discipline de la charge de travail.

Le postmortem a été clair : « Drop‑in replacement » concerne la correction, pas la prévisibilité de performance. Ils ont ajouté une porte de migration : prouver la classe de goulot sous une charge proche de la production avant de changer de moteur.

Mini-récit 2 : l’optimisation qui s’est retournée contre eux

Une plateforme retail avait un classique : une requête de rapport lente en heures de pointe. Quelqu’un a proposé un gain facile : augmenter les buffers par connexion et les tailles de tables temporaires pour éviter les débordements sur disque. Sur le papier, raisonnable. En pratique, cela a déclenché une cascade.

Le changement est passé un vendredi (non pas par malveillance, mais par optimisme). Pendant le pic, le nombre de connexions a explosé à cause d’un problème en amont avec le pooler. Chaque connexion pouvait maintenant allouer plus de mémoire lors des tris et des joins. La pression mémoire a monté, le noyau a commencé à swapper, et soudain chaque requête — rapide comme lente — est devenue plus lente. La latence est devenue éruptive, puis saturée.

Ils ont rollbacké vite, mais la leçon est restée : les réglages mémoire se multiplient avec la concurrence. « Plus gros buffers » n’est pas un repas gratuit ; c’est un buffet où chaque thread prend un plateau. Si votre appli invite 1 000 convives, il vous faut un videur, pas une cuisine plus grande.

Ils l’ont réglé de la manière ennuyeuse. D’abord, ils ont réparé le pooling et mis des caps de concurrence raisonnables. Ensuite ils ont ajouté un index ciblé et réécrit la requête de rapport pour pré‑agréger. Après cela, un réglage modéré des tables temporaires a réellement aidé parce qu’il n’était plus en compétition avec le swap.

Mini-récit 3 : la pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe services financiers utilisait MySQL et MariaDB dans différentes unités. L’infra était bordélique, mais leur hygiène opérationnelle était étonnamment bonne. Chaque hôte de base de données avait un script standard « capture d’évidence » : il prenait processlist, InnoDB status, compteurs clés, stats IO, et un extrait du slow log, et stockait le tout avec des timestamps.

Un après‑midi, ils ont reçu une rafale de timeouts. L’astreinte a exécuté le script deux fois, à deux minutes d’intervalle. La seconde exécution montrait une montée des Innodb_log_waits et un saut soudain de await disque. Le processlist montrait beaucoup de petites écritures attendant le commit. Ce n’était pas un problème de plan de requête ; c’était un problème de latence d’écriture.

Parce qu’ils avaient les données, ils n’ont pas perdu de temps à débattre des différences de moteurs. Ils ont vérifié les logs noyau et trouvé des resets au niveau d’un NVMe. La couche RAID/controlleur masquait suffisamment le problème pour ressembler à une « lenteur base de données ». Ils ont basculé proprement vers un standby et remplacé le device le jour même.

La correction n’était pas brillante. Elle était répétable et fondée sur des preuves. Leur « script ennuyeux » a évité une semaine de tuning guidé par la superstition et une dispute sur quel fork de MySQL est « plus stable ».

Erreurs courantes : symptôme → cause racine → correction

C’est la partie où les systèmes de production confessent leurs péchés. Chaque item est spécifique parce que les conseils vagues sont la façon dont les incidents se reproduisent.

1) Symptom : « Le CPU est élevé, mais les requêtes sont simples »

Cause racine : concurrence et contention. Beaucoup de requêtes « simples » peuvent se battre sur des lignes chaudes, des indexes ou des mutex internes. Autre coupable fréquent : comparaisons de collation inefficaces ou fonctions sur colonnes indexées qui empêchent l’utilisation de l’index.

Correction : Identifiez les requêtes principales par temps total, confirmez l’usage des indexes avec EXPLAIN, et réduisez la concurrence côté appli. Envisagez d’ajouter ou de remodeler des indexes pour éviter les points chauds.

2) Symptom : « La latence pique toutes les quelques minutes, puis revient à la normale »

Cause racine : rafales de flush IO, comportement de checkpoint, ou incidents de stockage. Aussi : jobs en arrière‑plan ou DDL lancés selon un planning.

Correction : Corrélez les pics avec iostat await, Innodb_os_log_fsyncs, et le comportement des pages dirty. Décalez les rafales hors pics, batcher les écritures, et vérifiez la santé du stockage.

3) Symptom : « Le lag du réplica augmente, mais le CPU est bas »

Cause racine : l’application SQL du réplica est sérialisée par des lignes chaudes, grosses transactions, DDL, ou une configuration d’application mono‑thread. Un CPU bas ne signifie pas sain ; cela peut signifier en attente d’E/S ou de verrous.

Correction : Inspectez la croissance des relay logs, vérifiez les transactions longues, activez/ajustez la réplication multi‑thread quand applicable, et réduisez la taille des transactions sur le primaire.

4) Symptom : « Beaucoup de connexions, mais Threads_running n’est pas si élevé »

Cause racine : churn de connexions et overhead ; l’application n’utilise pas de pooling, ou les health checks sont trop agressifs. Aussi : max_connections réglé haut pour « corriger » des erreurs, créant un rayon d’impact plus grand.

Correction : Corrigez le pooling, limitez la concurrence, utilisez un proxy/pooler si nécessaire, et gardez max_connections réaliste pour que la surcharge échoue rapidement plutôt que lentement.

5) Symptom : « Les deadlocks ont commencé après l’ajout d’un index »

Cause racine : les nouveaux chemins d’accès ont changé l’ordre d’acquisition des verrous. L’index n’a pas créé les deadlocks ; il a révélé des bugs d’ordre sous concurrence.

Correction : Standardisez l’ordre des updates dans le code, réduisez le périmètre transactionnel, et assurez des retries bornés pour les deadlocks. Utilisez des WHERE consistants et évitez les updates de plage sur des tables chaudes.

6) Symptom : « ALTER TABLE reste bloqué indéfiniment »

Cause racine : verrou métadonnées attendant derrière une transaction longue, souvent une transaction de lecture laissée ouverte par un pool de connexions.

Correction : Trouvez le bloqueur via processlist / InnoDB status, tuez ou terminez la transaction longue, et utilisez des stratégies de changement de schéma en ligne avec des garde‑fous opérationnels.

7) Symptom : « L’utilisation IO est faible, mais await est élevé »

Cause racine : mise en file dans la pile stockage, throttling, voisin bruyant, ou latence injectée par le firmware / contrôleur. %util peut mentir sur les devices modernes et en environnement virtualisé.

Correction : Faites confiance aux métriques de latence, vérifiez les logs du noyau, validez la santé du device, et envisagez d’isoler les I/O (volumes dédiés, QoS) avant de tuner InnoDB.

8) Symptom : « Les requêtes sont plus lentes après une mise à niveau »

Cause racine : régressions d’optimiseur, valeurs par défaut changeantes, comportement des statistiques modifié, ou modes SQL différents. MariaDB et MySQL divergent ici plus que certains ne l’admettent.

Correction : Capturez les plans EXPLAIN avant/après, rafraîchissez les stats, fixez les requêtes critiques avec des hints testés si nécessaire, et exécutez des tests de régression basés sur la charge — pas seulement des tests unitaires.

MariaDB vs MySQL : là où les différences font réellement mal

Si vous diagnostiquez des goulots d’étranglement, le choix du moteur compte surtout lorsqu’il change l’instrumentation, le comportement de l’optimiseur, le contrôle de concurrence, et les semantiques de réplication. Voici comment y penser sans en faire une guerre de religion.

Instrumentation : pouvez‑vous voir ce qui se passe ?

Le performance schema de MySQL 8.0 est un point fort pour le diagnostic en production : attentes, étapes, digests de statements et contention peuvent être rendus visibles. MariaDB a aussi de l’instrumentation, mais la maturité et les valeurs par défaut diffèrent selon les versions et les distributions.

Conseil opérationnel : choisissez un « golden path » pour la capture d’évidence par moteur. Pour MySQL 8.0, appuyez‑vous sur performance_schema et les résumés de digest. Pour MariaDB, validez ce qui est activé et fiez‑vous à un mélange de compteurs d’état, InnoDB status, slow logs, et échantillonnage ciblé.

Dérive de l’optimiseur : même requête, plan différent

C’est là que le « drop‑in » devient épicé. Une requête peut être correcte sur les deux moteurs et choisir des ordres de jointure ou des chemins d’index différents. Sous charge, ce n’est pas académique ; c’est la différence entre « ça marche » et « ça fond ».

Conseil opérationnel : traitez les changements de moteur et les upgrades majeurs comme des événements de changement de plan. Baselinez les plans des requêtes critiques et testez‑les en régression sur des distributions de données représentatives.

Comportement de concurrence : gestion des threads et contention interne

MariaDB est souvent discutée avec le comportement du thread pool dans certaines distributions. MySQL a son propre comportement de threads et des améliorations au fil des versions. Dans les deux cas, le thème est le même : la concurrence non contrôlée transforme la latence en loterie.

Conseil opérationnel : limitez la concurrence côté application et à la frontière de la base de données. Ne « réglez » pas une surcharge en augmentant max_connections. C’est comme élargir l’entonnoir alors que le tuyau est bouché.

Réplication et clustering : outils différents, modes de défaillance différents

L’écosystème MySQL penche vers GTIDs, semi‑sync, group replication et modèles managés. MariaDB apparaît souvent avec Galera sur le terrain. Les deux peuvent être fiables. Les deux peuvent aussi échouer de façons ressemblant à une « lenteur aléatoire » quand le vrai problème est le flow control, les conflits de certification, ou la sérialisation de l’application.

Conseil opérationnel : décidez ce dont vous avez besoin : montée en lecture, basculement, multi‑writer, ou distribution géographique. Puis concevez pour cela. N’« activez » pas le clustering comme une fonctionnalité de performance ; c’est une fonctionnalité de disponibilité avec des coûts de latence.

Réalité des moteurs de stockage : la plupart des gens utilisent InnoDB

Si votre charge repose sur InnoDB, beaucoup de vos goulots seront les mêmes sur les deux : misses du buffer pool, flush du redo log, doublewrite, vidage de pages, et contention de verrous. C’est pourquoi la checklist fonctionne indépendamment de la marque.

Conseil opérationnel : si quelqu’un affirme « MariaDB est plus lente » ou « MySQL est plus lente », demandez : plus lente sur quel type de goulot ? Et où sont les preuves ?

FAQ

1) Quelle est la façon la plus rapide de trouver le goulot ?

Classez‑le : CPU‑bound vs IO‑bound vs lock‑bound vs replication‑bound. Utilisez vmstat, iostat -x, et SHOW FULL PROCESSLIST avant de toucher à la config.

2) Dois‑je commencer par le slow query log ou performance schema ?

Commencez par ce qui est déjà activé et fiable. Le slow query log est peu contraignant et fonctionne sur les deux moteurs. performance_schema est plus riche sur MySQL 8.0, mais vous devez savoir ce qui est activé et l’overhead acceptable.

3) Si la latence disque est élevée, dois‑je juste augmenter le buffer pool ?

Seulement si vous avez de la RAM disponible et que votre working set peut en bénéficier. Si votre dataset actif est bien plus grand que la mémoire, augmenter le buffer pool montre des rendements décroissants. Corrigez la sélectivité et les indexes d’abord.

4) Pourquoi « Threads_running » est‑il plus important que « Threads_connected » ?

Connected vous dit combien de clients sont branchés. Running vous dit combien consomment activement du CPU ou attendent dans le moteur. Des counts élevés de running se corrèlent souvent avec la contention et la mise en file.

5) Le lag de réplication est‑il un problème de base ou d’application ?

Les deux. Le lag vient souvent de la forme des transactions applicatives : grosses transactions, lignes chaudes, ou DDL. La config DB (parallélisme d’application, capacité IO) peut aider, mais elle ne peut pas rendre parallèle une charge fondamentalement sérialisée.

6) Quand est‑il acceptable de changer les paramètres de durabilité pour la performance ?

Quand le métier accepte explicitement le risque et que vous pouvez quantifier le blast radius. Sinon, traitez la durabilité comme non négociable et corrigez le vrai goulot (stockage, taux de transactions, batching).

7) Ma requête utilise un index mais reste lente — pourquoi ?

Parce que « utilise un index » peut toujours signifier scanner une large plage, effectuer des I/O aléatoires, ou trier ensuite. Regardez rows examined, « Using filesort », « Using temporary », et si l’index correspond au filtre + ordre.

8) Comment éviter les régressions de plan lors des upgrades MariaDB/MySQL ?

Baselinez les requêtes critiques (plans EXPLAIN et exécution sur des données représentatives), exécutez des replays de charge ou des tests de montée en charge, et comparez les digests des requêtes avant/après. Traitez les changements d’optimiseur comme une fonctionnalité et un risque.

9) Quelle est la cause la plus courante de « c’est lent parfois » ?

La mise en file : stalls fsync, attentes de verrous, ou rafales de flush en arrière‑plan. La latence intermittente signifie généralement que quelque chose bloque, pas que le CPU a oublié de calculer.

10) Devrait‑on migrer de MySQL vers MariaDB (ou inversement) pour corriger la performance ?

Pas comme premier geste. Changer de moteur peut aider dans des cas spécifiques (fonctionnalités, instrumentation, modèle de réplication), mais la plupart des gains de performance viennent des corrections de requêtes/index, de la discipline transactionnelle, et de la correction du stockage.

Prochaines étapes qui ne ruineront pas votre week-end

Voici le plan pratique que je donnerais à une rotation d’astreinte qui veut moins de mystères à 3h du matin et des systèmes plus prévisibles :

  1. Standardiser la capture d’évidence : un script/runbook qui prend processlist, InnoDB status, compteurs clés et stats IO hôte avec timestamps.
  2. Activer au moins une source de traçage de requête : slow query log avec des seuils raisonnables, plus capture de plan pour les principaux coupables.
  3. Établir votre taxonomie de goulots : CPU, IO, verrous, réplication, mémoire. Taggez les incidents en conséquence. Les patterns émergent vite.
  4. Corriger la forme de la charge : raccourcir les transactions, ajouter les indexes composés manquants, réduire les joins fan‑out, et limiter la concurrence en amont.
  5. Ensuite seulement, optimiser : ajustez la mémoire et les réglages IO en vous basant sur des preuves, et faites‑le avec des étapes de rollback.

L’avantage réel MariaDB vs MySQL en matière de performance n’est pas un paramètre secret. C’est la clarté opérationnelle. Vous gagnez en trouvant rapidement le goulot, en corrigeant la cause réelle, et en refusant de confondre activité et progrès.

← Précédent
Histoire de Xeon : comment les processeurs serveur ont imposé les règles à tous
Suivant →
Verrouillage MariaDB vs SQLite : comment éviter les erreurs « Busy »

Laisser un commentaire