# Deux index composites qui divisent par dix un temps de requête maison

> Un SELECT filtré sur deux colonnes sans index composite force MySQL à parcourir une table entière. Diagnostic à l'EXPLAIN et correctif par migration dbDelta.

- Auteur : WordPress Développement
- Publié le : 2022-11-29
- Mis à jour le : 2022-11-29
- Catégorie : Extensions
- URL : https://www.wpmoderne.fr/extensions/deux-index-composites-diviser-temps-requete/

## L’essentiel

- EXPLAIN révèle le scan complet avant toute optimisation
- Un index composite respecte l'ordre des colonnes filtrées
- La migration passe par dbDelta, jamais par un ALTER manuel

`EXPLAIN SELECT * FROM wp_reservations WHERE statut = 'confirmee' AND date_debut > '2022-11-01'` : cette seule commande, lancée dans phpMyAdmin sur une table de réservations d'une extension maison, a suffi à expliquer pourquoi l'écran d'administration mettait plus de six secondes à s'afficher une fois la table passée à quelques dizaines de milliers de lignes.

La colonne `rows` du résultat d'`EXPLAIN` affichait un nombre proche du total de lignes de la table, et la colonne `key` restait vide : MySQL parcourait l'intégralité de la table à chaque affichage de l'écran, faute d'index utilisable pour cette combinaison de filtres.

## Le diagnostic à l'EXPLAIN

Avant toute correction, il faut confirmer le diagnostic plutôt que de deviner. La commande `EXPLAIN` placée devant une requête `SELECT` retourne un plan d'exécution : type de balayage (`type`), index envisagés (`possible_keys`), index réellement utilisé (`key`) et estimation du nombre de lignes examinées (`rows`). Une valeur `ALL` dans la colonne `type` signale un balayage complet de table, le signe le plus net d'un index manquant.

Sur la table en question, deux colonnes étaient systématiquement filtrées ensemble dans le code de l'extension : `statut` et `date_debut`. Un index existait déjà sur `date_debut` seule, posé lors d'une optimisation précédente, mais il n'aidait pas cette requête précise, car le moteur devait quand même vérifier le statut ligne par ligne après avoir localisé la plage de dates.

## Pourquoi un index simple ne suffit pas

> L'essentiel à retenir : EXPLAIN révèle le scan complet avant toute optimisation ; Un index composite respecte l'ordre des colonnes filtrées ; La migration passe par dbDelta, jamais par un ALTER manuel

Un index sur une seule colonne accélère les requêtes filtrées uniquement sur cette colonne. Dès qu'une requête combine deux conditions avec un `AND`, MySQL ne peut exploiter pleinement qu'un seul index à la fois dans la plupart des plans d'exécution simples : il utilise le premier pour réduire le nombre de lignes candidates, puis vérifie la seconde condition en parcourant ces lignes une par une.

Un index composite, couvrant les deux colonnes dans un ordre précis, permet au moteur de localiser directement les lignes qui satisfont les deux conditions sans étape de filtrage supplémentaire. L'ordre des colonnes dans l'index compte : une requête qui filtre d'abord sur l'égalité (`statut = 'confirmee'`) puis sur une plage (`date_debut > ...`) tire parti d'un index qui place la colonne d'égalité en premier.

## La migration par dbDelta

La correction ne passe pas par un `ALTER TABLE` lancé à la main sur la base de production, car rien ne garantit que cette commande sera rejouée sur les environnements de développement, de recette ou chez un client hébergé ailleurs. La bonne pratique WordPress consiste à décrire la structure complète de la table dans une chaîne SQL et à la confier à `dbDelta()`, qui compare la définition fournie à la structure existante et applique uniquement les différences.

```
function extension_maj_schema_reservations() {
    global $wpdb;
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';

    $table = $wpdb->prefix . 'reservations';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        statut VARCHAR(20) NOT NULL,
        date_debut DATETIME NOT NULL,
        client_id BIGINT UNSIGNED NOT NULL,
        PRIMARY KEY  (id),
        KEY statut_date_debut (statut, date_debut)
    ) $charset_collate;";

    dbDelta( $sql );
}
```

Le nom donné à l'index composite, `statut_date_debut`, reflète l'ordre des colonnes qu'il couvre : c'est une convention utile pour retrouver rapidement, des mois plus tard, quelle requête il sert à accélérer. La fonction s'accroche typiquement à un hook de mise à jour de version, comparé à une option stockée en base, pour ne s'exécuter qu'une fois par montée de version de l'extension.

## Vérifier le résultat

Après application de la migration, relancer `EXPLAIN` sur la même requête doit afficher `statut_date_debut` dans la colonne `key` et une valeur bien plus faible dans `rows`. Sur le cas traité ici, le nombre de lignes examinées est passé de plusieurs dizaines de milliers à quelques centaines, et le temps de réponse de l'écran d'administration est descendu sous la demi-seconde.

- Vérifier l'index avec `SHOW INDEX FROM wp_reservations` pour confirmer sa présence après migration.
- Mesurer avant et après avec `EXPLAIN`, pas seulement à l'œil sur le temps de chargement de la page.
- Ne pas multiplier les index composites au hasard : chaque index ralentit légèrement les écritures et occupe de l'espace disque.

## Notre verdict

Un index composite bien choisi coûte quelques lignes de migration et se paie immédiatement en confort d'utilisation dès que la table dépasse quelques milliers de lignes. La difficulté n'est pas technique mais méthodologique : sans passage par `EXPLAIN`, on optimise souvent la mauvaise requête ou on pose un index inutile. Ce diagnostic porte sur une correction ponctuelle a posteriori ; il ne remplace pas une réflexion sur la conception initiale du schéma, qui reste un sujet à part entière.
