# Traquer les requêtes SQL lentes sur WordPress avec Query Monitor

> Panneau Queries, requêtes dupliquées, boucles N+1 dans WP_Query, index manquants : la méthode complète pour débusquer les lenteurs SQL avec Query Monitor.

- Auteur : Clément Hadrot
- Publié le : 2021-10-14
- Mis à jour le : 2021-10-14
- Catégorie : Performance
- URL : https://wpmoderne.dev.wordpress-developpement.fr/performance/requetes-sql-lentes-query-monitor/

## L’essentiel

- Le panneau Queries classe les requêtes par composant, temps et nombre d'appels
- Les requêtes dupliquées trahissent presque toujours une boucle mal construite
- Un index manquant sur postmeta se repère grâce à l'explain intégré

Un site WordPress qui rame n'est presque jamais un problème de PHP trop lent : dans la grande majorité des audits qu'on mène, le coupable se trouve du côté de la base de données. Des dizaines, parfois des centaines de requêtes SQL redondantes, exécutées à chaque chargement de page, souvent invisibles tant qu'on ne regarde pas sous le capot. Et l'outil pour regarder sous le capot, c'est Query Monitor.

Ce plugin gratuit est devenu un réflexe dans notre boîte à outils de développement : on l'active systématiquement en environnement de recette, et on s'en sert dès qu'un client signale un temps de chargement anormal. Ce tutoriel détaille comment lire ses panneaux, repérer les requêtes dupliquées, débusquer les boucles N+1 dans `WP_Query`, et identifier une indexation manquante.

## Installer Query Monitor et lire le panneau Queries

L'installation ne demande rien de particulier : c'est un plugin classique, disponible sur le répertoire officiel, à activer uniquement sur les environnements de développement et de recette, jamais en production sur un site à fort trafic, car il ajoute une charge de mesure non négligeable.

Une fois actif, une barre apparaît dans l'admin bar avec le nombre de requêtes SQL et le temps total passé en base pour la page en cours. Le panneau **Queries** qu'on ouvre depuis cette barre liste chaque requête avec :

- Le texte SQL complet de la requête
- Le composant qui l'a déclenchée (cœur WordPress, un plugin, ou le thème)
- La fonction PHP appelante, avec la pile d'appel
- Le temps d'exécution individuel

Un onglet permet aussi de filtrer par composant, ce qui est très utile pour isoler rapidement les requêtes provenant d'une extension tierce plutôt que du thème qu'on développe soi-même.

## Repérer les requêtes dupliquées

Query Monitor regroupe automatiquement les requêtes identiques et affiche leur nombre d'occurrences. C'est souvent le premier signal d'alerte : une même requête exécutée quinze ou vingt fois sur une seule page ne devrait presque jamais arriver, sauf cas très spécifiques.

```
-- Exemple typique de requête dupliquée repérée dans le panneau Queries
SELECT option_value FROM wp_options WHERE option_name = 'active_plugins' LIMIT 1
-- Répétée 24 fois sur le chargement d'une seule page
```

Dans la grande majorité des cas, ce genre de duplication vient d'un appel à une fonction comme `get_option()` ou `get_post_meta()` placé à l'intérieur d'une boucle, sans mise en cache. Le correctif est presque toujours le même : sortir l'appel de la boucle, ou s'appuyer sur le cache d'objets de WordPress qui met normalement en cache ces valeurs après le premier appel — sauf si un plugin mal codé vide ce cache prématurément.

> L'essentiel à retenir : Le panneau Queries classe les requêtes par composant, temps et nombre d'appels ; Les requêtes dupliquées trahissent presque toujours une boucle mal construite ; Un index manquant sur postmeta se repère grâce à l'explain intégré

## Le cas classique : la boucle N+1 dans WP_Query

C'est le scénario qu'on retrouve le plus souvent sur les thèmes personnalisés : une `WP_Query` qui récupère une liste d'articles, puis une deuxième requête exécutée pour chaque article à l'intérieur de la boucle, pour aller chercher une métadonnée, un terme de taxonomie, ou l'auteur.

```
// Anti-pattern classique : une requête supplémentaire par article
$query = new WP_Query( array( 'posts_per_page' => 50 ) );

while ( $query->have_posts() ) {
    $query->the_post();
    $note = get_post_meta( get_the_ID(), 'note_moyenne', true ); // requête à chaque itération
    $auteur = get_the_author_meta( 'display_name', get_the_author_meta( 'ID' ) ); // idem
}
```

Sur une liste de 50 articles, ce pattern peut facilement générer plus de 300 requêtes SQL rien que pour les métadonnées, sans compter les requêtes annexes du thème. On l'a mesuré précisément sur un site client : **340 requêtes** déclenchées pour afficher une simple page d'archive de 50 articles, contre 28 après correction.

La solution consiste à précharger les métadonnées en une seule requête avant la boucle, grâce à `update_post_meta_cache()`, ou plus simplement en s'assurant que l'argument `update_post_meta_cache` de `WP_Query` reste activé (il l'est par défaut) et en évitant les appels à des fonctions qui ne passent pas par le cache d'objets.

```
// Version corrigée : précharger les meta en une seule requête
$post_ids = wp_list_pluck( $query->posts, 'ID' );
update_meta_cache( 'post', $post_ids );

while ( $query->have_posts() ) {
    $query->the_post();
    $note = get_post_meta( get_the_ID(), 'note_moyenne', true ); // servi depuis le cache
}
```

## Identifier une indexation manquante

Quand une requête individuelle prend plusieurs dizaines voire centaines de millisecondes à elle seule, même en dehors de toute duplication, le problème vient souvent d'un index manquant sur la table concernée. Query Monitor affiche le temps par requête, ce qui permet de repérer facilement les plus coûteuses dans le panneau Queries trié par durée.

La table `wp_postmeta` est la cible la plus fréquente : sa colonne `meta_value` n'est pas indexée par défaut, et une requête qui filtre dessus via `meta_query` peut devenir très lente dès que la table dépasse quelques dizaines de milliers de lignes.

```
-- Pour vérifier le plan d'exécution d'une requête lente copiée depuis Query Monitor
EXPLAIN SELECT post_id FROM wp_postmeta
WHERE meta_key = 'note_moyenne' AND meta_value > '4'
ORDER BY meta_value DESC;
```

Si la colonne `type` du résultat affiche `ALL` plutôt qu'un accès par index, c'est le signe d'un balayage complet de table. Dans ce cas, on ajoute un index composite dédié, ou on migre la donnée dans une table personnalisée plus adaptée si le volume est important.

> Avant d'ajouter un index à la main sur `wp_postmeta`, vérifiez qu'un plugin ne le fait pas déjà pour la meta_key concernée : plusieurs extensions de recherche ou de filtres produits gèrent leur propre table d'index pour éviter de toucher au cœur.

## Les autres panneaux à ne pas négliger

Le panneau Queries n'est pas le seul utile pour la performance :

- **Hooks & Actions** : utile pour voir tous les callbacks accrochés à un hook donné et repérer un plugin qui en abuse
- **HTTP API Calls** : montre les appels réseau sortants, souvent une cause de lenteur bien plus grave qu'une requête SQL
- **Object Cache** : indique le taux de succès du cache d'objets, très parlant pour juger si un cache persistant type Redis apporterait un vrai gain

## En résumé

Query Monitor ne corrige rien tout seul, mais c'est l'outil qui transforme une impression vague de lenteur en diagnostic précis et actionnable. La méthode qu'on applique systématiquement : trier par nombre d'occurrences pour chasser les doublons, trier par temps pour chasser les requêtes coûteuses individuellement, et remonter la pile d'appel jusqu'à la ligne de code exacte à corriger. Sur la plupart des sites qu'on a audités cette année, une bonne heure passée avec ce plugin a suffi à diviser le temps de génération de la page par deux ou trois, sans toucher à l'hébergement.
