MySQL vs MariaDB : requêtes tueuses dans WordPress — comment réparer sans réécrire le site

Cet article vous a aidé ?

WordPress ne « devient pas lent au hasard ». Il ralentit selon des schémas très spécifiques et reproductibles, généralement dans la base de données. Un jour votre page d’accueil se charge en 400 ms, le lendemain elle met 8 secondes et vos workers PHP s’entassent comme des bagages à un vol annulé.

La partie difficile n’est pas de choisir MySQL ou MariaDB. La partie difficile est d’attraper les requêtes tueuses que WordPress (et ses plugins) introduisent discrètement : index manquants, autoload surchargé, LIKE pathologiques, tempêtes de verrous, écrasements de tables temporaires et hypothèses de cache jamais vraies. Vous pouvez corriger la plupart de ces problèmes sans réécrire le site, et sans transformer votre base en projet artisanal.

MySQL vs MariaDB : ce qui compte réellement pour WordPress

WordPress n’est pas une suite de benchmarks. C’est un ensemble de schémas de requêtes prévisibles (le noyau) plus des schémas imprévisibles (plugins) plus la blessure auto-infligée occasionnelle (code personnalisé, mauvaises importations, optimisations « utiles »).

Choisir MySQL ou MariaDB compte moins que ce que les gens pensent. Les deux utilisent InnoDB, les deux peuvent très bien fonctionner, et les deux peuvent très mal tourner si vous laissez quelques tables croître sans garde-fous.

Différences rapides qui apparaissent en production

  • Les comportements par défaut et de version diffèrent. Les valeurs par défaut de MySQL 8 (et les changements d’optimiseur) peuvent se comporter différemment de MariaDB 10.x/11.x dans des cas limites. Pour WordPress, la plupart des problèmes viennent encore du schéma et de la charge de travail, pas d’un optimiseur génial.
  • Les outils d’observabilité diffèrent. Performance Schema et le schéma sys de MySQL 8 sont solides et largement documentés. MariaDB a des équivalents, mais vous utiliserez parfois d’autres vues ou activerez d’autres plugins.
  • La nostalgie du query cache est un piège. Si vous choisissez MariaDB parce que vous avez entendu « le query cache aide WordPress », arrêtez. On ne répare pas un site dynamique moderne en ressuscitant un mutex global de 2009.
  • Le thread pool peut compter. Le thread pool de MariaDB (selon l’édition/la compilation) peut aider en cas de forte rotation de connexions. Mais si vous lancez 800 enfants PHP-FPM et que chacun ouvre sa connexion, vous avez construit une usine à connexions, pas un site.
  • Les sémantiques GTID/réplication diffèrent. Cela affecte la bascule/HA plus que la latence brute des requêtes, mais les pannes WordPress sont souvent « la base de données » même quand le déclencheur est la latence de réplication.

Ce que vous devriez décider (plutôt que de choisir une marque par loyauté)

Pour WordPress, vos vrais points de décision sont :

  • Pouvez-vous obtenir une version majeure moderne ? Les anciens MySQL 5.6/5.7 et les anciennes builds MariaDB offrent moins d’outils de diagnostic et plus de pièges. Exécutez quelque chose de récent et maintenu.
  • Avez-vous une familiarité on-call ? La meilleure base de données est celle que votre équipe peut déboguer à 02:00 sans exécuter une danse d’interprétation sur une réplique en production.
  • Avez-vous un plan pour la dérive du schéma/index ? Les installations WordPress sont des flocons de neige. Vos requêtes tueuses ne sont pas théoriques — elles sont dans ce flocon.

Faits intéressants & contexte historique (court et utile)

  1. MySQL a été acquis par Sun en 2008, et Sun a été acquis par Oracle en 2010 — c’est la généalogie d’entreprise derrière de nombreux débats « MySQL vs MariaDB ».
  2. MariaDB a été créée par les auteurs originaux de MySQL comme fork pour préserver une voie communautaire après l’acquisition par Oracle.
  3. WordPress a commencé à l’ère de MySQL 4, ce qui explique certains choix de schéma durables (comme la forte dépendance aux tables meta) qui n’étaient pas conçus pour des requêtes analytiques modernes.
  4. InnoDB est devenu le moteur par défaut dans MySQL 5.5 ; avant cela, MyISAM était courant. Certaines « astuces » d’optimisation héritées supposent encore le comportement MyISAM et sont mauvaises pour WordPress aujourd’hui.
  5. MySQL 8 a supprimé complètement le query cache ; MariaDB l’a conservé plus longtemps. Cette divergence a alimenté des mythes selon lesquels « MariaDB est plus rapide pour WordPress », même quand le goulot d’étranglement est PHP ou des index manquants.
  6. Performance Schema a mûri énormément dans MySQL 5.7 et 8 ; si vous ne l’avez utilisé qu’une fois et que vous l’avez détesté, vous l’avez probablement utilisé à ses débuts maladroits.
  7. La table wp_postmeta de WordPress est volontairement générique (paires clé/valeur), ce qui facilite l’écriture de plugins mais complique la conservation de performances de la base à grande échelle.
  8. UTF8MB4 est devenu le choix pratique par défaut pour WordPress moderne afin de supporter Unicode complet (y compris les emojis, que votre équipe marketing utilisera que ça vous plaise ou non).

Les requêtes tueuses de WordPress (et pourquoi elles nuisent)

La plupart des incidents de « performance de base de données WordPress » se réduisent à un petit ensemble de modes de défaillance. L’astuce est de les reconnaître rapidement et d’appliquer des correctifs ennuyeux.

1) Surcharge d’autoload dans wp_options

WordPress charge les options avec autoload='yes' à chaque requête. À chaque requête. Si cet ensemble atteint des mégaoctets d’array sérialisés — merci les plugins — vous avez ajouté une taxe à chaque vue de page avant même d’exécuter une seule requête « intéressante ».

Symptômes : le TTFB augmente, le CPU grimpe, les requêtes semblent petites mais fréquentes, les misses du cache d’objets deviennent coûteuses et les pages d’administration donnent l’impression d’avancer dans la mélasse.

2) La taxe des meta-tables : wp_postmeta et consorts

Les tables meta sont flexibles, mais elles transforment « trouver des posts avec X » en « scanner beaucoup de lignes avec des jointures répétées ». Si un plugin construit une requête avec plusieurs conditions meta sans index de soutien, cela peut coincer le CPU et chasser des pages du buffer pool.

3) Index manquants ou erronés (surtout sur meta_key + meta_value)

Les index du noyau WordPress sont minimaux. Les plugins interrogent souvent des colonnes non indexées (ou indexées dans le mauvais ordre), provoquant des scans complets. À mesure que les données croissent, vous passez de « ça va » à « incendie » sans avertissement autre que le slow log que vous n’avez pas activé.

4) ORDER BY RAND() et autres crimes joyeux

Celle-ci mérite une mention spéciale : elle semble innocente et donne l’impression d’être une astuce. C’est aussi une bombe à retardement de performance sur toute table comptant plus qu’« un nombre mignon » de lignes.

Blague #1 : ORDER BY RAND() c’est comme demander à la base de données de mélanger un entrepôt une boîte à la fois.

5) Verrouillage et contention : mises à jour, transients, cron et comportement admin

WordPress a des schémas d’écriture qui peuvent causer de la contention : nettoyage des transients, tâches planifiées (WP-Cron), migrations de plugins et opérations en masse dans l’admin. Si vous avez du trafic et qu’un administrateur lance une modification en masse, vous pouvez obtenir des attentes de verrous et une file de requêtes.

6) Tables temporaires et déversements sur disque

Quand MySQL/MariaDB ne peut pas conserver les résultats intermédiaires en mémoire, il déverse sur disque. Si votre tmp dir est sur un stockage lent ou si vous saturez les IOPS, la latence explosera lors des tris, groupements et jointures complexes. WordPress peut déclencher cela via la recherche, des plugins de reporting et des écrans d’administration « filtrer tout ».

7) Confusion des caches : object cache vs page cache vs database cache

Le caching WordPress est un système en couches, et les équipes les confondent régulièrement :

  • Page cache (CDN, reverse proxy) réduit le travail PHP et DB pour le trafic anonyme.
  • Object cache (Redis/Memcached) réduit les lectures répétées au sein et entre requêtes.
  • Database cache (buffer pool) garde en mémoire les pages fréquemment consultées.

Quand vous « activez le caching » et que rien ne s’améliore, c’est généralement parce que vous avez activé la mauvaise couche ou que votre taux de hit est mauvais parce que les motifs autoload/meta génèrent trop de clés uniques.

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

Ceci est le plan pour quand le site est lent et que vous essayez d’éviter d’empirer la situation. L’objectif est d’identifier rapidement si vous êtes lié par le CPU, l’I/O, les verrous ou les connexions.

Premier : confirmer la classe de goulot (CPU, I/O, verrous ou connexions)

  • CPU-bound : CPU base de données élevé, beaucoup de « Sending data », nombreux scans complets, buffer pool inefficace parce que les requêtes sont inefficaces.
  • I/O-bound : latence lecture/écriture élevée, misses du buffer pool, déversements tmp sur disque, stockage lent ou volume saturé.
  • Lock-bound : de nombreux threads en « Waiting for table metadata lock » ou « Waiting for row lock », transactions de longue durée.
  • Connection-bound : trop de connexions/threads, changement de contexte, max connections atteint, stampede PHP-FPM.

Deuxième : capturer les principaux coupables (ne pas deviner)

  • Activez/inspectez le slow query log (fenêtre courte) ou utilisez les vues Performance Schema.
  • Récupérez des snapshots d’activité via SHOW PROCESSLIST / information_schema.
  • Exécutez EXPLAIN sur la pire requête et recherchez « Using temporary; Using filesort » et les scans complets.

Troisième : appliquez le correctif le moins risqué qui élimine le plus de douleur

  • Si c’est l’autoload : réduisez autoload, nettoyez les transients, confirmez la réduction de taille.
  • Si ce sont des index manquants : ajoutez des index composites ciblés (avec plan de rollback).
  • Si c’est le verrouillage : trouvez la transaction longue et arrêtez/évitez-la ; ajustez les timeouts prudemment.
  • Si c’est l’I/O : augmentez le buffer pool (si la mémoire le permet), corrigez la taille des tmp tables, déplacez tmpdir sur un stockage rapide.
  • Si ce sont les connexions : ajoutez une stratégie de pooling (ou au moins réduisez la concurrence PHP-FPM) et fixez des limites raisonnables.

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

Voici des tâches réelles que vous pouvez exécuter aujourd’hui sur un hôte Linux typique. Chacune inclut : la commande, ce que signifie une sortie typique, et la décision suivante.

Task 1: Identify server flavor and version (you can’t tune what you can’t name)

cr0x@server:~$ mysql -e "SELECT VERSION() AS version, @@version_comment AS comment, @@version_compile_machine AS arch;"
+-------------------------+------------------------------+------+
| version                 | comment                      | arch |
+-------------------------+------------------------------+------+
| 8.0.36                  | MySQL Community Server - GPL | x86_64 |
+-------------------------+------------------------------+------+

Ce que cela signifie : MySQL 8.0.36. Si c’était MariaDB, comment mentionnerait MariaDB et les formats de version incluraient souvent -MariaDB.

Décision : Utilisez les outils MySQL 8 (Performance Schema, schéma sys). Si vous êtes sur une version ancienne, prévoyez du temps pour une mise à jour car beaucoup de problèmes de « lenteur mystérieuse » sont plus faciles à voir sur des versions modernes.

Task 2: Check current connection pressure

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections'; SHOW VARIABLES LIKE 'max_connections';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 186   |
+-------------------+-------+
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 412   |
+----------------------+-------+
+-----------------+------+
| Variable_name   | Value|
+-----------------+------+
| max_connections | 500  |
+-----------------+------+

Ce que cela signifie : Vous opérez près de la limite lors des pics. Si PHP-FPM a 300 workers et que chacun utilise sa propre connexion, vous pouvez atteindre le plafond lors d’un pic de trafic.

Décision : Si Threads_connected est régulièrement > 60–70% de max_connections, réduisez la concurrence de l’application et/ou ajoutez une stratégie de proxy/pooling (ou au moins des connexions persistantes réglées de façon sûre). Vérifiez aussi le cache de threads.

Task 3: Confirm whether slow query logging is on (and where)

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time'; SHOW VARIABLES LIKE 'log_output';"
+---------------------+----------------+
| Variable_name       | Value          |
+---------------------+----------------+
| slow_query_log      | OFF            |
| slow_query_log_file | /var/log/mysql/slow.log |
+---------------------+----------------+
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| long_query_time | 10.000000 |
+-----------------+-------+
+---------------+-------+
| Variable_name | Value  |
+---------------+-------+
| log_output    | FILE  |
+---------------+-------+

Ce que cela signifie : Le slow log est désactivé et le seuil est trop élevé pour le diagnostic WordPress.

Décision : Activez-le temporairement avec un seuil bas (0.5–1s) pendant les pics, puis remettez-le à un niveau normal. Capturez des preuves au lieu d’avoir des impressions.

Task 4: Enable slow query log for a short diagnostic window

cr0x@server:~$ mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.5; SET GLOBAL log_queries_not_using_indexes = 'ON';"
...output omitted...

Ce que cela signifie : De nouvelles requêtes lentes seront enregistrées. log_queries_not_using_indexes peut être bruyant ; vous l’utilisez comme une lampe torche, pas comme un mode de vie.

Décision : Laissez-le tourner 10–30 minutes sous trafic typique, puis analysez. Désactivez-le ou augmentez les seuils après avoir obtenu assez d’échantillons.

Task 5: Summarize slow log offenders quickly

cr0x@server:~$ sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
Reading mysql slow query log from /var/log/mysql/slow.log
Count: 212  Time=3.12s (661s)  Lock=0.00s (0s)  Rows=10234.0 (2169608), root[root]@localhost
  SELECT SQL_NO_CACHE * FROM wp_postmeta WHERE meta_key = 'S' AND meta_value LIKE '%S%' ORDER BY meta_id DESC LIMIT N
Count: 88  Time=1.44s (126s)  Lock=0.03s (2s)  Rows=1.0 (88), wp[wp]@10.0.2.10
  SELECT option_value FROM wp_options WHERE autoload = 'yes'

Ce que cela signifie : Vous avez un motif de recherche dans les meta faisant un LIKE '%...%' sur meta_value (douleur) et une requête d’autoload fréquente qui peut ramener une grosse charge utile.

Décision : Pour la recherche meta : corrigez la fonctionnalité ou contraignez-la ; les index ne sauveront pas un LIKE à wildcard au début sur une colonne texte longue. Pour l’autoload : mesurez la taille et élaguez.

Task 6: Check top current queries and lock states

cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;"
+-----+------+----------------+------+---------+------+------------------------------+-----------------------+
| Id  | User | Host           | db   | Command | Time | State                        | Info                  |
+-----+------+----------------+------+---------+------+------------------------------+-----------------------+
| 821 | wp   | 10.0.2.10:53214| wpdb | Query   |  12  | Waiting for table metadata lock | ALTER TABLE wp_postmeta ... |
| 844 | wp   | 10.0.2.11:53302| wpdb | Query   |   8  | Sending data                 | SELECT ... FROM wp_posts ...|
| 865 | wp   | 10.0.2.12:54100| wpdb | Query   |  15  | Waiting for row lock         | UPDATE wp_options SET ...   |
+-----+------+----------------+------+---------+------+------------------------------+-----------------------+

Ce que cela signifie : Un ALTER TABLE bloque la métadonnée et un UPDATE attend des verrous de lignes. C’est un incident de contention, pas un incident « il faut plus de RAM ».

Décision : N’exécutez pas de DDL pendant les pics. Si un plugin migre le schéma en direct, mettez-le en pause/rollback ou faites-le en fenêtre de maintenance. Trouvez la transaction bloquante ensuite.

Task 7: Find InnoDB lock waits and the blocker

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,160p'
...
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 18392019, ACTIVE 14 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 865, OS thread handle 140055, query id 998201 10.0.2.12 wpdb updating
UPDATE wp_options SET option_value = '...' WHERE option_name = '_transient_timeout_wc_count_comments'
*** (2) TRANSACTION:
TRANSACTION 18392011, ACTIVE 22 sec fetching rows
...

Ce que cela signifie : Les transients sont impliqués ; un plugin (ici, comportement type WooCommerce) martèle wp_options et entre en collision.

Décision : Déplacez les transients vers Redis/Memcached (cache d’objets persistant) pour réduire les écritures en base, et/ou réduisez la concurrence du cron. Assurez-vous aussi que wp_options.option_name est indexé (il devrait l’être).

Task 8: Measure buffer pool pressure (are you I/O bound?)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages%';"
+----------------------------------+----------+
| Variable_name                    | Value    |
+----------------------------------+----------+
| Innodb_buffer_pool_read_requests | 94811234 |
| Innodb_buffer_pool_reads         | 2211903  |
+----------------------------------+----------+
+----------------------------------+--------+
| Variable_name                    | Value  |
+----------------------------------+--------+
| Innodb_buffer_pool_pages_total   | 524288 |
| Innodb_buffer_pool_pages_free    | 120    |
| Innodb_buffer_pool_pages_dirty   | 18240  |
+----------------------------------+--------+

Ce que cela signifie : Reads vs read requests vous donne un ratio d’hit approximatif : 1 - reads/read_requests. Ici c’est correct, mais pages_free est minime (buffer pool entièrement utilisé).

Décision : Si votre ensemble de travail ne tient pas et que la latence disque est élevée, augmentez innodb_buffer_pool_size (dans les limites sûres de la RAM). Si le ratio d’hit est déjà bon, concentrez-vous sur l’efficacité des requêtes et le verrouillage.

Task 9: Confirm tmp table spills (classic hidden latency)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables'; SHOW VARIABLES LIKE 'tmp_table_size'; SHOW VARIABLES LIKE 'max_heap_table_size';"
+-------------------------+--------+
| Variable_name           | Value  |
+-------------------------+--------+
| Created_tmp_disk_tables | 92341  |
| Created_tmp_tables      | 145220 |
+-------------------------+--------+
+-------------------+----------+
| Variable_name     | Value    |
+-------------------+----------+
| tmp_table_size    | 16777216 |
+-------------------+----------+
+-------------------+----------+
| Variable_name     | Value    |
+-------------------+----------+
| max_heap_table_size | 16777216 |
+-------------------+----------+

Ce que cela signifie : Un ratio élevé de tmp tables sur disque corrèle souvent avec des tris/groupes lents. Des limites à 16 Mo sont conservatrices.

Décision : Si vous avez de la RAM libre, augmentez les deux tailles ensemble (elles plafonnent effectivement les tmp tables en mémoire). Localisez aussi quelles requêtes causent cela — ne tournez pas seulement des boutons en espérant le meilleur.

Task 10: Inspect the worst query plan (EXPLAIN, not prayer)

cr0x@server:~$ mysql -e "EXPLAIN SELECT p.ID FROM wp_posts p JOIN wp_postmeta pm ON pm.post_id=p.ID WHERE pm.meta_key='color' AND pm.meta_value='blue' AND p.post_type='product' AND p.post_status='publish' ORDER BY p.post_date DESC LIMIT 20;"
+----+-------------+-------+------+---------------+------+---------+-------------------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref               | rows   | Extra                       |
+----+-------------+-------+------+---------------+------+---------+-------------------+--------+-----------------------------+
|  1 | SIMPLE      | pm    | ref  | meta_key      | meta_key | 767  | const             | 980000 | Using where; Using filesort |
|  1 | SIMPLE      | p     | eq_ref | PRIMARY     | PRIMARY | 8     | wpdb.pm.post_id   | 1      | Using where                 |
+----+-------------+-------+------+---------------+------+---------+-------------------+--------+-----------------------------+

Ce que cela signifie : On scanne presque un million de lignes dans wp_postmeta pour ce meta_key puis on filtre par meta_value. Le filesort suggère un travail supplémentaire pour l’ordre.

Décision : Ajoutez un index composite qui correspond au motif du filtre (généralement (meta_key, meta_value(…prefix…), post_id) selon les types et la sélectivité). Puis relancez EXPLAIN et validez la réduction du nombre de lignes examinées.

Task 11: Measure autoload payload size in wp_options

cr0x@server:~$ mysql -e "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb, COUNT(*) AS autoload_rows FROM wp_options WHERE autoload='yes';"
+-------------+--------------+
| autoload_mb | autoload_rows|
+-------------+--------------+
| 14.73       | 3129         |
+-------------+--------------+

Ce que cela signifie : ~15 Mo d’options sont chargées à chaque requête. Ce n’est pas « un petit overhead ». C’est une facture récurrente.

Décision : Identifiez les plus gros éléments autoload et désactivez l’autoload pour les options non essentielles, ou supprimez les options obsolètes de plugins morts. Visez < 1–2 Mo pour la plupart des sites ; les sites plus gros peuvent justifier plus, mais 15 Mo est généralement accidentel.

Task 12: Find the biggest autoload offenders (safely)

cr0x@server:~$ mysql -e "SELECT option_name, ROUND(LENGTH(option_value)/1024,1) AS kib FROM wp_options WHERE autoload='yes' ORDER BY LENGTH(option_value) DESC LIMIT 15;"
+-----------------------------------+--------+
| option_name                        | kib    |
+-----------------------------------+--------+
| myplugin_big_cache_blob            | 2048.4 |
| theme_mods_mytheme                 | 612.7  |
| rewrite_rules                      | 512.2  |
| widget_custom_html                 | 498.9  |
+-----------------------------------+--------+

Ce que cela signifie : Un plugin bourre l’autoload avec des « blobs » de plusieurs mégaoctets. Aussi, certaines options core (comme rewrite_rules) peuvent être volumineuses mais généralement pas multi-mégaoctets.

Décision : Pour les blobs de plugin : mettez autoload sur ‘no’ (si c’est sûr), migrez vers le cache d’objets, ou empêchez le plugin de persister de grosses données calculées. Pour les options core : envisagez de vider et régénérer les règles de réécriture (prudemment) si elles sont corrompues.

Task 13: Check table sizes and growth (spot the real monster)

cr0x@server:~$ mysql -e "SELECT table_name, ROUND((data_length+index_length)/1024/1024,1) AS total_mb FROM information_schema.tables WHERE table_schema='wpdb' ORDER BY (data_length+index_length) DESC LIMIT 10;"
+--------------+----------+
| table_name   | total_mb |
+--------------+----------+
| wp_postmeta  | 8420.5   |
| wp_posts     | 2150.2   |
| wp_options   | 390.4    |
| wp_comments  | 280.7    |
+--------------+----------+

Ce que cela signifie : wp_postmeta domine. C’est normal à l’échelle — mais cela signifie que les choix d’index comptent et que « des metas partout » vous puniront.

Décision : Concentrez l’optimisation sur les patterns d’accès à wp_postmeta et réduisez les requêtes lourdes en meta sur les endpoints à fort trafic (recherche, filtres, listings produits).

Task 14: Check for long-running transactions (hidden lock bombs)

cr0x@server:~$ mysql -e "SELECT trx_id, trx_state, trx_started, TIMESTAMPDIFF(SECOND,trx_started,NOW()) AS trx_age_s, trx_mysql_thread_id FROM information_schema.innodb_trx ORDER BY trx_started ASC LIMIT 10;"
+--------+----------+---------------------+----------+---------------------+
| trx_id | trx_state| trx_started         | trx_age_s| trx_mysql_thread_id |
+--------+----------+---------------------+----------+---------------------+
| 183910 | RUNNING  | 2025-12-31 10:21:02 | 1843     | 512                 |
+--------+----------+---------------------+----------+---------------------+

Ce que cela signifie : Une transaction est ouverte depuis ~30 minutes. Dans l’univers WordPress, c’est suspect. Cela peut bloquer le purge, provoquer la croissance des historiques et amplifier la contention de verrous.

Décision : Identifiez la requête/l’utilisateur qui la maintient (processlist) et corrigez le comportement applicatif (souvent un rapport/export, une migration de plugin ou un job admin).

Task 15: Check disk latency and saturation (because physics still runs the datacenter)

cr0x@server:~$ iostat -xz 1 5
Linux 6.8.0 (server)  12/31/2025  _x86_64_  (8 CPU)

avg-cpu:  %user %nice %system %iowait  %steal  %idle
          22.1  0.0   6.4    18.8     0.0   52.7

Device            r/s     w/s   rkB/s   wkB/s  await  svctm  %util
nvme0n1         980.0   420.0 52000.0 18000.0  12.4   0.8   98.7

Ce que cela signifie : Utilisation très élevée et await significatif. Si les tmp tables déversent ou si les misses du buffer pool augmentent, vous ressentirez immédiatement cela comme une latence accrue du site.

Décision : Réduisez d’abord le travail disque (meilleurs index, dimensionnement du buffer pool, éviter les déversements tmp) avant d’acheter des disques plus rapides. Si vous êtes déjà sur NVMe et que tout est saturé, vous faites probablement des requêtes absurdes à grande échelle.

Points chauds du schéma : wp_options, wp_postmeta, wp_posts

wp_options : autoload et transients

Ce qui vous tue : autoload surchargé + churn des transients.

  • Autoload : traitez-le comme une configuration de démarrage, pas comme un store de cache.
  • Transients : si vous n’avez pas de cache d’objets persistant, les transients atterrissent dans wp_options et se transforment en écritures sous trafic.

Stratégie de correction :

  • Mesurez la taille de l’autoload, réduisez-la et maintenez-la réduite.
  • Déplacez les transients vers Redis/Memcached (plugin de cache d’objets persistant + backend).
  • Empêchez les plugins d’écrire de gros blobs comme options autoloadées.

wp_postmeta : faites payer les requêtes meta

Ce qui vous tue : jointures répétées et filtres non indexés.

Les index du noyau WordPress incluent typiquement post_id et meta_key. Les plugins filtrent souvent sur meta_value, trient par celui-ci ou font des requêtes de plage sur des valeurs numériques stockées comme chaînes. C’est ainsi que vous vous retrouvez avec une base « correcte jusqu’à ce que non ».

Stratégie de correction :

  • Ajoutez des index composites sélectifs correspondant à votre charge réelle (pas une frénésie « indexer tout »).
  • Pour les plages numériques : assurez-vous que les requêtes castent de façon cohérente, et envisagez de stocker les valeurs numériques dans des tables dédiées si l’occasion se présente. Sinon, indexez un préfixe ou repensez la fonctionnalité.
  • Pour les recherches textuelles sur meta_value : arrêtez les LIKE à wildcard de début. Utilisez de vrais moteurs de recherche ou limitez la portée.

wp_posts : schémas prévisibles, faciles à heurter

Les requêtes autour de post_type, post_status, post_date et des jointures de taxonomie sont courantes. Les index core aident souvent, mais les requêtes personnalisées qui trient sur des expressions non indexées ou joignent des metas excessifs peuvent toujours provoquer des déversements tmp et des filesorts.

Blague #2 : Une « optimisation » de base de données qui ajoute cinq jointures pour éviter une requête, c’est comme prendre trois vols pour éviter une escale.

Tuning qui fonctionne sur les deux (et ce qui diffère)

Voici la règle opinionnée : faites le tuning à partir de preuves, pas à partir d’articles de blog. Pour WordPress, la plupart des gains viennent du modelage du schéma et de la charge de travail. Ensuite viennent le dimensionnement mémoire et les limites de concurrence.

Commencez par ces réglages (parce qu’ils comptent vraiment)

1) innodb_buffer_pool_size

Si votre serveur DB est dédié à MySQL/MariaDB, une base courante est 60–75% de la RAM pour le buffer pool. Sur des machines à usage mixte, soyez conservateur. La performance WordPress s’améliore souvent davantage en faisant tenir les pages chaudes en mémoire que par tout autre changement isolé.

2) innodb_log_file_size / dimensionnement du redo log

Trop petits, les redo logs peuvent provoquer une pression de checkpoint lors de rafales d’écriture (imports, orages de cron). Trop gros, ils peuvent allonger le temps de récupération après crash. Dimensionnez-les selon les schémas d’écriture ; ne les mettez pas à « n’importe quoi que quelqu’un a tweeté ».

3) Taille des tmp tables et emplacement du tmpdir

Si les tmp tables sur disque sont élevées, augmenter tmp_table_size et max_heap_table_size (ensemble) peut aider — jusqu’à un certain point. Assurez-vous aussi que votre tmpdir est sur un stockage local rapide, pas sur un montage réseau lent.

4) Concurrence : gardez des connexions saines

WordPress ne bénéficie pas d’une concurrence illimitée. Si vous satuez la base, la latence augmente et le débit chute souvent. Limitez les workers PHP-FPM selon la capacité DB mesurée, pas selon « nombre de cœurs × 20 ».

5) Réglages d’observabilité (pour prouver les améliorations)

Activez ce dont vous avez besoin quand vous en avez besoin : slow logs, consumers Performance Schema (lorsque pertinent), et métriques OS de base. Laissez-vous des miettes de pain.

Où MySQL et MariaDB diffèrent en pratique

  • Différences d’optimiseur : vous verrez parfois un plan diverger. Quand cela arrive, la correction est généralement la même : ajoutez le bon index, réécrivez la pire requête (souvent dans un plugin) ou changez le pattern d’accès.
  • Instrumentation : sur MySQL 8, appuyez-vous sur Performance Schema et le schéma sys. Sur MariaDB, vous préférerez peut-être information_schema et l’état du moteur, plus les vues propres à MariaDB selon la compilation.
  • Disponibilité du thread pool : peut changer le comportement sous tempête de connexions. Ça n’excuse pas des tempêtes de connexions.

Une idée de fiabilité à garder en tête, attribuée à John Allspaw (paraphrasée) : « La fiabilité, c’est ce que vous faites avant l’incident, pas pendant. »

Trois micro-histoires d’entreprise issues du terrain

Mini-histoire 1 : L’incident causé par une mauvaise hypothèse

Ils faisaient tourner un site de contenu occupé avec WordPress et un plugin commerce populaire. Le trafic n’était pas massif, mais il était en pics : campagnes, envois d’e-mails et la mention occasionnelle sur un grand forum.

Un nouveau responsable engineering a poussé un effort de « standardisation » de la base. Le plan : remplacer MariaDB par MySQL 8 sur toute la flotte parce que « MySQL 8 est plus rapide et plus moderne ». Cette partie n’était pas folle. La mauvaise hypothèse était : la base est interchangeable tant que le schéma est le même.

Ils ont migré proprement, les tests ont passé, et la première semaine a semblé correcte. Puis une vente a commencé. La latence du checkout est passée de sub-seconde à plusieurs secondes, et ils ont commencé à voir des « Error establishing a database connection ». L’équipe a blâmé le nouveau moteur et a commencé à revenir en arrière dans la panique.

Le vrai problème : pendant la migration, ils ont aussi changé des SQL modes et des valeurs par défaut. Une requête de plugin qui avait hérité un chemin médiocre avec des conversions implicites a désormais eu un plan différent et a commencé à utiliser un chemin d’index terrible. Sous charge, cela a causé des déversements tmp et a saturé l’I/O. La même requête sur MariaDB avait eu la « chance » de choisir un plan moins atroce.

La correction fut peu glamour : ajouter un index composite correspondant au filtre et ajuster la requête du plugin pour éviter un tri calculé. Après cela, MySQL 8 était correct. La leçon : les changements de version exposent les problèmes latents du schéma. La base ne les a pas trahis ; elle a simplement cessé de tolérer le désordre.

Mini-histoire 2 : L’optimisation qui a mal tourné

Une autre entreprise avait le problème classique : recherche dans wp-admin et filtrage produits lents. Quelqu’un a proposé « ajoutez juste des index partout dans wp_postmeta ». Ils ont créé plusieurs index composites larges, y compris de longs préfixes de meta_value, et les ont déployés en heures ouvrables parce que « l’ODL en ligne est maintenant sûr ».

Pendant un court instant, les recherches se sont accélérées. Puis la latence d’écriture a augmenté. Les imports et jobs cron ont commencé à expirer. Le CPU DB est monté et est resté élevé. L’utilisation disque a gonflé car chaque nouvel index a gonflé la table, et le buffer pool ne pouvait plus garder l’ensemble de travail chaud.

Pire : certaines requêtes n’utilisaient pas les nouveaux index du tout, car les formes de prédicat ne correspondaient pas à l’ordre des colonnes d’index. D’autres les utilisaient mais payaient un coût élevé de maintenance sur les écritures. Ils avaient optimisé une petite tranche de lectures en taxant chaque écriture et chaque ligne de cache.

Ils ont récupéré en faisant ce qu’ils auraient dû faire d’abord : identifier les 5 principaux motifs de requêtes et n’indexer que ceux-ci, avec une largeur minimale. Ils ont supprimé les index morts, planifié correctement les changements de schéma et déplacé les fonctionnalités riches en transients vers Redis.

La leçon durable : « plus d’index » n’est pas une stratégie. C’est une manière de convertir la douleur de lecture en douleur d’écriture tout en consommant la RAM.

Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Un média avait un déploiement multisite WordPress et une petite équipe SRE qui insistait sur deux pratiques « ennuyeuses » : (1) slow query log activé avec un seuil raisonnable, logroté et archivé, et (2) revue hebdomadaire des principaux coupables avec les propriétaires de plugins.

Les gens levaient les yeux parce que tout semblait correct la plupart du temps. Puis une nouvelle version de plugin a été déployée qui changeait la gestion des posts liés. Elle a introduit une requête qui joignait wp_posts à wp_postmeta plusieurs fois et ajoutait un tri sur une expression non indexée.

Parce que le slow log était déjà en place, l’équipe a vu le nouveau coupable en quelques heures. Ils n’ont pas eu besoin d’une salle de crise ni d’un roman policier. Ils avaient l’empreinte de la requête et une baseline avant/après.

La correction fut chirurgicale : un index, un changement de configuration du plugin pour limiter le pool de posts liés, et une option de rollback si l’index causait des douleurs d’écriture. Les utilisateurs n’ont rien remarqué. La direction n’en a jamais entendu parler, ce qui est le plus grand compliment que la production puisse faire.

C’est ce que la rigueur ennuyeuse achète : moins de week-ends héroïques.

Erreurs courantes : symptôme → cause racine → correction

1) Symptom: homepage slow, but database CPU is low

Cause racine : accumulation de connexions et attentes de verrous ; les requêtes attendent, elles ne calculent pas.

Correction : vérifiez SHOW PROCESSLIST pour les états de verrou ; trouvez et arrêtez les transactions longues ; planifiez le DDL hors pics ; réduisez la concurrence PHP-FPM.

2) Symptom: admin pages (especially “Products” or “Orders”) are painfully slow

Cause racine : filtrage et tri riches en meta provoquant des déversements tmp ou des scans complets.

Correction : slow log + EXPLAIN de la requête admin ; ajoutez un index composite pour le prédicat dominant ; augmentez les limites tmp si nécessaire ; désactivez les colonnes/filtres coûteux dans les vues de liste admin.

3) Symptom: sudden spikes in write I/O and lock waits every few minutes

Cause racine : rafales WP-Cron, nettoyage de transients ou tâches planifiées de plugins.

Correction : déplacez le cron vers le cron système ; assurez-vous qu’il n’y a qu’un seul exécuteur ; migrez les transients vers un cache d’objets persistant ; réduisez la fréquence des tâches planifiées si possible.

4) Symptom: search is slow and gets worse with content growth

Cause racine : requêtes LIKE '%term%' sur de grands champs texte, souvent en postmeta.

Correction : arrêtez le leading-wildcard LIKE à l’échelle. Utilisez un vrai moteur de recherche full-text adapté au contenu, limitez la portée de la recherche ou externalisez-la. Les index ne sauveront pas fiablemement ce pattern.

5) Symptom: “Error establishing a database connection” under load

Cause racine : max connections atteint, surcharge de scheduling des threads, ou DB bloquée sur I/O/verrous provoquant des timeouts de connexions.

Correction : mesurez Threads_connected, Max_used_connections ; réduisez les workers PHP-FPM ; appliquez de la rétro-pression ; assurez-vous que la DB n’est pas saturée en I/O ; évitez les transactions longues.

6) Symptom: adding an index made performance worse

Cause racine : coût de maintenance des index, ensemble de travail gonflé, ou optimiseur choisissant le mauvais index.

Correction : validez avec EXPLAIN et les stats de requêtes ; supprimez les index inutilisés/faible valeur ; gardez les index étroits et alignés sur les prédicats ; envisagez les histogrammes (MySQL) seulement après les bases.

7) Symptom: database is fast, but replication lag explodes during traffic

Cause racine : charge d’écriture élevée (options/transients), grosses transactions, ou limitations d’application single-thread selon la configuration.

Correction : réduisez les écritures (cache d’objets persistant), évitez les grosses transactions, ajustez les réglages de réplication adaptés au moteur/version ; n’utilisez pas les réplicas pour les lectures s’ils sont systématiquement en retard.

8) Symptom: intermittent stalls during backups or analytics jobs

Cause racine : job de backup/ETL causant de la contention I/O ou des lectures consistantes de longue durée impactant purge/undo.

Correction : planifiez les backups hors-pic ; utilisez des méthodes de sauvegarde non bloquantes ; throttlez ; assurez-vous que la taille undo/redo correspond à la charge ; surveillez l’I/O et l’âge des transactions.

Listes de contrôle / plan étape par étape

Étapes : stabiliser une base WordPress lente sans réécrire

  1. Définissez ce que « lent » signifie. Capturez latence p95/p99 des pages, CPU DB, await disque et connexions actives pendant une fenêtre dégradée.
  2. Activez brièvement le slow query logging. Utilisez un seuil 0.5–1s pendant 10–30 minutes sous trafic réel.
  3. Identifiez les 3 empreintes de requête principales. Ne courez pas après la longue traîne.
  4. EXPLAIN la pire requête. Recherchez scans complets, filesort et usage de tmp tables.
  5. Corrigez la plus grosse taxe « always-on » en premier. D’habitude autoload bloat ou une requête meta chaude sur un endpoint à fort trafic.
  6. Ajoutez un index ciblé à la fois. Validez l’amélioration et surveillez la latence des écritures.
  7. Déplacez les transients vers un cache d’objets persistant. Cela réduit le churn d’écriture et la contention de verrous dans wp_options.
  8. Contrôlez la concurrence. Limitez les workers PHP-FPM à ce que la DB peut supporter ; évitez les stampedes de connexions.
  9. Re-vérifiez les déversements tmp et les statistiques du buffer pool. Ajustez la mémoire seulement après que la forme des requêtes est raisonnable.
  10. Remettez les seuils du slow log à la normale. Gardez-le activé à une valeur sensée et faites la rotation des logs.
  11. Notez ce qui a changé. Pas pour la bureaucratie — pour que le prochain incident ne soit pas de l’archéologie.

Checklist : avant d’ajouter un index

  • Avez-vous le texte exact de la requête (ou l’empreinte normalisée) ?
  • Avez-vous exécuté EXPLAIN et capturé les estimations « rows » ?
  • Pouvez-vous tester en staging avec un volume de données proche de la prod ?
  • Connaissez-vous l’impact sur les écritures (imports, cron, checkouts) ?
  • Avez-vous un plan de rollback (drop index) et une fenêtre de maintenance si nécessaire ?

Checklist : nettoyage sûr de l’autoload

  • Mesurez le MB actuel d’autoload et les principaux coupables.
  • Confirmez quelles options appartiennent à des plugins/themes décommissionnés.
  • Changez les flags d’autoload prudemment (préférez les réglages du plugin si disponibles).
  • Remesurez l’autoload MB et surveillez la latence frontend.
  • Gardez un snapshot/backup avant les modifications massives.

FAQ

1) Dois-je choisir MySQL ou MariaDB pour la performance WordPress ?

Choisissez celui que vous pouvez exécuter en version actuelle et supportée avec une bonne familiarité on-call. Pour WordPress, l’hygiène du schéma et des requêtes l’emporte la plupart du temps sur le choix du moteur.

2) MariaDB est-elle « plus rapide pour WordPress » à cause du query cache ?

Non. Le query cache n’est pas la solution moderne que l’on imagine, et MySQL 8 l’a supprimé pour de bonnes raisons. Utilisez le page/object caching et corrigez les motifs de requêtes à la place.

3) Quel est le correctif unique le plus important pour de nombreux sites WordPress ?

Le nettoyage de l’autoload dans wp_options, plus un cache d’objets persistant pour réduire le churn des transients. Ce n’est pas glamour, mais c’est souvent le meilleur ROI.

4) J’ai activé Redis object cache ; pourquoi MySQL est-il encore lent ?

Parce que Redis ne corrige pas les requêtes meta non indexées, les déversements tmp ou la contention due à des transactions longues. Vérifiez le taux de hit du cache puis continuez le diagnostic des principales requêtes lentes.

5) Puis-je corriger des requêtes meta lentes sans réécrire le plugin ?

Souvent oui : ajoutez un ou deux index composites ciblés et réduisez la portée des filtres/tris via la configuration. Si le plugin fait un LIKE '%term%' sur meta_value, il faudra peut‑être changer la fonctionnalité, pas l’index.

6) Augmenter innodb_buffer_pool_size est‑il toujours sûr ?

C’est sûr seulement si l’hôte a de la RAM disponible pour l’OS, le cache de fichiers et les autres services. Un oversizing provoque du swapping, et le swap transforme les bases en œuvre d’art de performance (mauvaise).

7) Pourquoi je vois « Waiting for table metadata lock » ?

Généralement parce que quelqu’un exécute du DDL (ALTER TABLE) pendant que le trafic continue, ou qu’une transaction longue bloque les changements de métadonnées. Planifiez le DDL hors pic et évitez les transactions longues.

8) Comment savoir si je suis I/O-bound ou CPU-bound ?

Vérifiez les métriques disque au niveau OS (await, %util) et les lectures/misses du buffer pool InnoDB. Await élevé et beaucoup de misses pointent vers l’I/O. CPU élevé avec beaucoup de scans complets pointe vers l’inefficacité des requêtes.

9) Dois‑je exécuter les lectures WordPress sur des réplicas ?

Seulement si vous pouvez tolérer la latence de réplica et si vous avez confirmé que votre charge est suffisamment orientée lecture pour en bénéficier. WordPress a des écritures surprenantes (options, sessions/transients via plugins) qui peuvent maintenir les réplicas en retard pendant les pics.

10) Que faire si la requête lente est dans le core WordPress ?

Il est rare que le core soit le principal coupable à l’échelle ; c’est généralement un plugin ou une forme de données spécifique (meta/options massifs). Si c’est vraiment le core, commencez toujours par indexer et mettre en cache plutôt que forker le core.

Conclusion : prochaines étapes réalisables cette semaine

MySQL contre MariaDB n’est pas votre problème de premier ordre. Votre problème de premier ordre est que les charges WordPress punissent les schémas et hypothèses bâclés, et que les plugins excellent à générer les deux.

Faites ceci, dans cet ordre :

  1. Activez le slow query logging pour une fenêtre contrôlée et extrayez les principaux coupables.
  2. Mesurez la taille d’autoload de wp_options et réduisez-la agressivement mais prudemment.
  3. EXPLAIN les pires requêtes meta et ajoutez des index composites ciblés — un à la fois.
  4. Déplacez les transients vers un cache d’objets persistant et prenez le cron au sérieux (un runner, planning prévisible).
  5. Mettez des garde-fous sur la concurrence pour éviter que la base ne soit submergée de connexions.
  6. Re-vérifiez les métriques disque et les tmp tables. Si vous déversez toujours sur disque, corrigez cela ensuite.

Si vous ne faites rien d’autre : arrêtez de deviner. Allumez les lumières (slow log + métriques basiques) et laissez la base de données vous dire la vérité. La plupart du temps, elle le fera.

← Précédent
Licences VMware ESXi en 2026 : changements, coûts et meilleures alternatives (Proxmox inclus)
Suivant →
Proxmox « pve-apt-hook failed » : pourquoi les mises à jour sont bloquées et comment débloquer en toute sécurité

Laisser un commentaire