# L’index MySQL manquant sur wp_postmeta ralentit un annuaire de 38 000 fiches

> Une recherche par champ personnalisé qui prenait quelques millisecondes se met à durer plusieurs secondes une fois l'annuaire passé à 38 000 fiches : la table wp_postmeta en est la cause.

- Auteur : Clément Hadrot
- Publié le : 2025-05-09
- Mis à jour le : 2025-05-09
- Catégorie : Thèmes
- URL : https://wpmoderne.dev.wordpress-developpement.fr/themes/index-mysql-wp-postmeta-annuaire-38000-fiches/

## L’essentiel

- wp_postmeta stocke tout en lignes clé-valeur, sans typage ni index par défaut
- Un filtre par meta_value déclenche un parcours complet de table au-delà d'un certain volume
- Un index composé ciblé règle le problème sans changer de structure de données

`SELECT post_id FROM wp_postmeta WHERE meta_key = 'ville' AND meta_value = 'Lyon'` : cette requête, en apparence anodine, mettait plus de quatre secondes à répondre sur un annuaire professionnel qui venait de franchir les 38 000 fiches, alors qu'elle répondait en quelques millisecondes six mois plus tôt. Rien n'avait changé dans le thème classique qui l'exécutait : seul le volume de données avait grossi, jusqu'à révéler une faiblesse structurelle de la table `wp_postmeta`.

Ce diagnostic concerne un thème classique qui interroge directement les champs personnalisés via `WP_Query` ou des requêtes SQL directes. Il ne traite pas l'indexation des gabarits d'un site en édition complète, qui repose sur une mécanique différente sans lien avec cette table.

## Comprendre la structure du problème : une table clé-valeur générique

`wp_postmeta` stocke chaque champ personnalisé sous forme d'une ligne indépendante, avec un `post_id`, une `meta_key` et une `meta_value` de type texte long. Cette conception générique permet à WordPress d'accepter n'importe quel champ sans modifier le schéma de la base, mais elle a un coût : par défaut, seules les colonnes `post_id` et `meta_key` bénéficient d'un index. La colonne `meta_value`, elle, n'est pas indexée, pour une raison simple : son typage texte long ne se prête pas à un index classique sur toute sa longueur.

```
wp_postmeta
├── meta_id       (clé primaire)
├── post_id       (index)
├── meta_key      (index partiel, 191 caractères)
└── meta_value    (longtext, non indexé)
```

## Le symptôme : une requête qui dégrade avec le volume, pas avec la complexité

Sur les premières dizaines de milliers de fiches, une requête filtrant par `meta_key` et `meta_value` reste rapide parce que MySQL trouve vite les lignes correspondant à la clé, avant de comparer les valeurs une à une. Passé un certain seuil, propre à chaque configuration de serveur, le nombre de lignes à comparer pour une même clé devient trop élevé pour rester performant sans index dédié sur la valeur elle-même.

> L'essentiel à retenir : wp_postmeta stocke tout en lignes clé-valeur, sans typage ni index par défaut ; Un filtre par meta_value déclenche un parcours complet de table au-delà d'un certain volume ; Un index composé ciblé règle le problème sans changer de structure de données

## Le diagnostic avec EXPLAIN

La commande `EXPLAIN` devant la requête fautive confirme le problème en révélant un balayage de type `ALL` plutôt qu'un accès par index :

```
EXPLAIN SELECT post_id FROM wp_postmeta
WHERE meta_key = 'ville' AND meta_value = 'Lyon';

+----+-------------+------------+------+---------------+------+
| id | select_type | table      | type | possible_keys | key  |
+----+-------------+------------+------+---------------+------+
|  1 | SIMPLE      | wp_postmeta| ref  | meta_key      | NULL |
+----+-------------+------------+------+---------------+------+
```

La colonne `key` vide sur le filtre de valeur confirme que MySQL utilise l'index de `meta_key` pour restreindre le lot de lignes, puis compare chaque `meta_value` une par une à l'intérieur de ce lot, sans index supplémentaire pour accélérer cette seconde étape.

## La solution : un index composé, ciblé, pas une réécriture de schéma

Réécrire le schéma de `wp_postmeta` n'est ni réaliste ni souhaitable : cette table est utilisée par le cœur de WordPress et par de nombreuses extensions. La solution la plus stable consiste à ajouter un index composé limité aux premiers caractères de `meta_value`, suffisant pour les champs courts comme un nom de ville, un code postal ou un statut :

```
ALTER TABLE wp_postmeta
ADD INDEX idx_meta_key_value (meta_key, meta_value(20));
```

Le chiffre 20 correspond à la longueur utile observée sur ce projet précis ; il doit être ajusté selon la nature réelle des valeurs filtrées. Cet index composé doit être créé via une migration versionnée dans le code du projet, par exemple à l'activation du thème avec `dbDelta()`, plutôt que directement en base de production sans trace dans le dépôt.

## Les limites de cette solution

- Un index composé sur `meta_value` n'aide que les comparaisons d'égalité stricte, pas les recherches partielles avec `LIKE '%valeur%'`.
- Chaque index supplémentaire ralentit légèrement les écritures : à ajouter uniquement sur les champs réellement filtrés en lecture fréquente.
- Au-delà de plusieurs champs filtrés simultanément, une table personnalisée dédiée, créée par le thème ou une extension, devient plus pertinente qu'une accumulation d'index sur `wp_postmeta`.

> Un index n'est jamais gratuit : il se pose sur le champ que la requête filtre réellement le plus souvent, pas sur celui qui semble le plus important dans l'interface d'administration.

## En résumé

Le ralentissement d'un annuaire volumineux ne vient presque jamais d'un défaut du thème lui-même, mais de la structure générique de `wp_postmeta` qui n'anticipe pas les filtres par valeur à grande échelle. Un index composé, ajouté avec discernement et suivi dans le code du projet, restaure des temps de réponse compatibles avec l'usage, sans nécessiter de migration vers une architecture de données plus lourde.
