Le WordPress d'aujourd'hui, décodé pour les développeurs

Thèmes

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.

Par Clément Hadrot • 9 mai 2025 • 5 min de lecture • Aucun commentaire
L'index MySQL manquant sur wp_postmeta ralentit un annuaire de 38 000 fiches

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.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi