Indexation MySQL: accélérer les requêtes sans casser le schéma
Dans une base MySQL, un index bien pensé transforme un océan de lignes en une file d’attente ordonnée. Cette précision se gagne au scalpel, pas à la machette. L’article plonge au cœur de l’Indexation dans MySQL pour performances, en observant ce qui fait réellement gagner des millisecondes, ce qui en coûte, et comment garder la carte et le terrain alignés quand la donnée grandit, change et surprend.
Pourquoi l’index est l’outil de vitesse le plus sûr ?
Parce qu’un index évite la lecture inutile de lignes en guidant MySQL vers les seules pages pertinentes, comme un plan de ville qui coupe court aux détours. En InnoDB, il rapproche aussi les données par clé, réduisant les allers-retours coûteux.
Dans MySQL, l’index n’est pas un simple répertoire, c’est une structure (souvent un B-Tree) qui dessine des raccourcis fiables. InnoDB place les données par la clé primaire dans un arbre « clustered » ; les index secondaires pointent d’abord sur cette clé, puis sur la ligne. Un « covering index » évite même l’accès à la table quand toutes les colonnes utiles s’y trouvent. Résultat : moins de pages lues, moins d’I/O, une latence qui retombe. Le bénéfice ne se limite pas aux filtres : un tri, un regroupement ou une jointure peuvent s’adosser à un index existant, et la requête glisse d’un nœud à l’autre sans friction. La mécanique reste délicate : une mauvaise sélectivité, un ordre de colonnes mal pensé, un opérateur non sargable, et l’index se tait, laissant le moteur ramer. Toute la maîtrise consiste à façonner l’index pour la forme réelle des requêtes, pas pour un idéal abstrait.
Quelles colonnes méritent vraiment un index ?
Celles qui réduisent le plus le champ de recherche et soutiennent les chemins critiques : filtres sélectifs, jointures fréquentes, tris répétés et fenêtres d’actualité (date, statut, propriétaire). Le reste alourdit les écritures sans gains palpables.
La pratique donne la boussole : là où les requêtes se rassemblent, l’index doit exister. Une colonne à forte cardinalité (« user_id », « email ») concentre le filtre ; une date de création ou de mise à jour sert la fraîcheur et les purges ; un statut discrimine si sa distribution est équilibrée. Les colonnes utilisées dans les prédicats d’égalité et d’intervalles s’y prêtent ; celles manipulées par des fonctions non indexables, beaucoup moins. L’ordre au sein d’un index composite suit le « chemin » des predicates : égalités d’abord, puis intervalles, enfin le tri. Ce principe simple évite des arbres magnifiques mais inutilisés.
| Heuristique | Indicateur | Décision d’index |
|---|---|---|
| Sélectivité | cardinalité/rows >= 0,1 à 0,3 | Indexer en priorité |
| Fréquence d’usage | Top 10 des requêtes par volume/latence | Indexer si la requête est critique |
| Rôle dans les jointures | Clés étrangères, colonnes de lookup | Indexer côté enfant et côté lookup |
| Tri/GROUP BY | Ordre récurrent sur de grands jeux | Mettre l’ordre en suffixe d’un composite |
| Distribution des valeurs | Skew élevé (valeur « chaude » dominante) | Indexer si couplé à un prédicat plus sélectif |
- Éviter d’indexer des colonnes booléennes isolées : sélectivité trop faible.
- Surveiller les colonnes très volatiles : coût d’écriture multiplié.
- Privilégier un composite judicieux plutôt que plusieurs index isolés redondants.
Comment choisir le bon type d’index en MySQL ?
Le B-Tree règne sur égalités, intervalles et ordre. Le FULLTEXT sert la recherche lexicale. Le SPATIAL balise l’espace. Des raffinements récents (fonctionnels, JSON multi‑valeurs, descendantes) affinent ces trajectoires.
MySQL 8 a élargi l’outillage. Le B-Tree classique couvre la majorité des parcours, y compris le tri si l’ordre colle à l’index. Les FULLTEXT sur VARCHAR/TEXT amènent le scoring lexical dans le moteur, à manier avec soin pour des corpus en expansion. Les SPATIAL en InnoDB donnent de l’air aux géométries, surtout avec des boîtes englobantes et des index R-Tree. Les index « fonctionnels » permettent d’indexer une expression (par exemple lower(email)) sans détourner des colonnes générées. Les key parts descendantes stabilisent les tris décroissants. Les multi-valued indexes pour JSON cernent les tableaux, utiles pour des tags ou droits. Chaque type porte ses règles, et le moteur ne « magie » pas : un like ‘%abc’ sans ancre en tête échappera toujours au B-Tree.
Quand le B-Tree suffit à presque tout
Pour WHERE =, BETWEEN, IN, ORDER BY et JOIN sur clés, le B-Tree reste la lame universelle. En composite, l’ordre des colonnes modèle le plan.
Le fameux « leftmost prefix rule » commande : l’index (user_id, created_at, id) sert pleinement un filtre sur user_id et un intervalle sur created_at, plus un tri par id. Inversement, commencer par created_at prive le moteur d’un filtre efficace si user_id arrive plus tard. Un index couvrant (colonne(s) de filtre + colonnes de projection) évite l’accès au clustered, excellent pour les endpoints gourmands. La cardinalité doit rester fidèle ; ANALYZE TABLE actualise des statistiques, et les histogrammes aident l’optimiseur dans les distributions tordues.
Texte, espace, JSON : quand changer d’outil
Le FULLTEXT répond aux correspondances floues, aux langues vivantes, aux requêtes booléennes ou en mode naturel. Les SPATIAL servent les points et polygones, souvent en tandem avec un filtre géographique grossier puis un raffinement précis. Les multi-valued indexes déplient des tableaux JSON pour conserver l’élégance côté modèle tout en évitant des tables pivot surexploitées.
Le choix s’évalue au regard du besoin dominant : pertinence sémantique ou correspondance exacte, précision géométrique ou bounding box, souplesse JSON ou rigidité relationnelle. Le moteur s’y plie, mais réclame un plan clair : un FULLTEXT n’accélère pas un tri, un SPATIAL n’accélère pas un LIKE, et un index JSON ne sauvera pas une jointure mal structurée.
| Type d’index | Cas d’usage fort | Limites/nuances |
|---|---|---|
| B-Tree | =, BETWEEN, IN, JOIN, ORDER BY | Règle du préfixe gauche, fonctions non sargables |
| FULLTEXT | Recherche lexicale, ranking | Maintenance coûteuse, pas pour tri/intervalle |
| SPATIAL (R-Tree) | ST_Contains, ST_Within, bounding box | Nécessite SRID correct, densité variable |
| Fonctionnel | Expressions (LOWER(), substrings utiles) | Stabilité de l’expression, cohérence collation |
| JSON multi‑valeurs | IN sur éléments de tableaux | Conception prudente pour éviter le bruit |
Que se passe-t-il quand l’index ralentit au lieu d’accélérer ?
Chaque index pèse sur les écritures, la mémoire et l’exploration de plans. Trop d’index, ou les mauvais, et la base perd sa foulée : flushs plus fréquents, verrous prolongés, plans hésitants.
InnoDB entretient ses arbres à chaque INSERT/UPDATE/DELETE. Les index secondaires multiplient ce travail et allongent les transactions sous charge. La redondance (index(a), index(a,b)) gaspille de la place et trouble l’optimiseur. Les fonctions appliquées côté WHERE empêchent l’usage d’un index pourtant disponible. Les collations inadaptées, les wildcards en tête, les conversions implicites (INT vs VARCHAR) désarment l’arbre. Parfois l’optimiseur parie mal sur un plan « index merge » qui se révèle plus coûteux que prévu ; EXPLAIN ANALYZE le prouve. La discipline consiste à traquer ces pièces mal ajustées.
| Piège | Symptôme | Remède |
|---|---|---|
| Index redondants | SHOW INDEX montre (a) et (a,b) | Supprimer l’index (a) si (a,b) existe |
| Fonctions non sargables | WHERE DATE(ts) = …, EXPLAIN = full scan | Index sur (ts) + intervalle, ou index fonctionnel |
| Collation/conversion | Comparaison VARCHAR vs INT, Using filesort | Types alignés, collation correcte, CAST côté écriture |
| Wildcard en tête | LIKE ‘%abc’ ignore l’index | FULLTEXT, trigrammes externes, ou ancrage |
| Skew massif | Valeur « chaude » fréquente, latence en pics | Composite avec filtre sélectif, histogrammes 8.0 |
- La suppression d’un index superflu rend parfois plus de service qu’un ajout précipité.
- Un UNIQUE bien placé documente la règle métier et accélère la recherche en même temps.
- Un index mal ordonné coûte silencieusement : lecture inutile, tri additionnel, cache érodé.
Comment diagnostiquer et prouver le gain de performance ?
En confrontant l’intuition au profilage : EXPLAIN pour l’intention, EXPLAIN ANALYZE pour le réel, métriques avant/après pour la preuve. Les compteurs InnoDB et performance_schema ferment la boucle.
Le plan affiche la stratégie : type d’accès (ref, range, index), clé choisie, colonnes utilisées. EXPLAIN ANALYZE ajoute la dure réalité des millisecondes et des lignes lues. SHOW INDEX révèle la cardinalité estimée et l’ordre. Les vues du performance_schema (events_statements_summary_by_digest) tracent la distribution des requêtes ; l’œil repère les digests dominants, les 95e et 99e percentiles qui guident les priorités. Un test A/B avec un index invisible mesure l’impact sans risque : le moteur l’ignore, la mesure tranche, la décision suit.
- Dresser la liste des requêtes dominantes par temps cumulé et p95/p99.
- Capturer EXPLAIN et EXPLAIN ANALYZE avant/après index.
- Surveiller handler_read_key/next, Innodb_buffer_pool_reads, rows examined.
- Valider la stabilité sous charge : débit, verrous, débit d’écriture.
Une routine efficace consiste à isoler un scénario précis : par exemple, une page listant des transactions par utilisateur, filtrée par statut et bornée par date. Un index (user_id, status, created_at desc) couvre le filtre et le tri, un suffixe id garantit la pagination stable. EXPLAIN montre un range sur user_id/status, filesort disparaît, la latence fond. En parallèle, la cardinalité de status reste modeste ; un histogramme affine l’estimation et sécurise le choix de plan quand la distribution bouge.
Mesurer sans bruit et comparer sans illusions
La mesure convainc quand elle isole le signal : mêmes données, même cache, mêmes paramètres. Un « warm-up » stabilise le buffer pool ; les tests s’exécutent plusieurs fois, la médiane tranche. Les variations de plan liées à des statistiques obsolètes se corrigent avec ANALYZE TABLE. En cas de doute, un hint temporaire ou un index invisible encadre l’expérience sans figer le système.
Les dashboards gagnent à montrer la courbe du p95 plutôt qu’une moyenne docile. Une baisse de 40 % du p95 vaut mieux qu’un -5 % du temps moyen, car elle allège les pointes qui saturent les files d’attente. Dans des systèmes bavards, ces pointes font et défont l’expérience ressentie.
Quelle stratégie d’indexation durable dans un système vivant ?
Une stratégie tient dans le temps si elle s’appuie sur l’observation continue, des garde‑fous et des déploiements réversibles. Les index évoluent avec les requêtes, pas l’inverse.
Les schémas prospèrent quand l’indexation s’inscrit dans le cycle de livraison. Les migrations utilisent ALGORITHM=INPLACE ou INSTANT et LOCK=NONE autant que possible, réduisant l’impact. Les index invisibles permettent d’expérimenter sans engager. Un inventaire régulier chasse les redondances ; les digests peu servis trahissent les index oubliés. Une nomenclature rend lisible l’intention (idx_user_created_desc), et les revues techniques exigent la preuve mesurée de chaque ajout. Les clés étrangères reçoivent leur index, les colonnes textuelles trouvent un FULLTEXT quand le besoin lexical s’impose, et les expressions récurrentes gagnent un index fonctionnel pour éviter des scans entêtés.
Ordonner l’ordre des colonnes, c’est écrire le plan
Dans un composite, l’ordre codifie la pensée : égalités, intervalles, tri. Déroger devient un choix, pas un accident. La pagination par curseur remercie les suffixes stables, le tri décroissant apprécie les parts descendantes, et les projections gagnent avec un index couvrant mesuré, pas obèse.
- (user_id, status, created_at desc, id) pour une liste récente et stable.
- (tenant_id, email) UNIQUE pour identité et lookup instantané.
- (created_at desc) seul sur des archivages ou fenêtres temporelles.
Gouvernance légère : des règles simples, vérifiées en continu
Un petit nombre de règles évite les dérives : pas d’index sans usage prouvé, pas de duplication, revue trimestrielle, suppression après période « invisible » réussie. La dette d’index ressemble à la poussière : elle s’invite sans bruit, puis étouffe les écritures.
Les alertes surveillent les symptômes : augmentation des rows examined, résurgence de filesort, hausse des I/O par transaction, montée des p95. Les correctifs suivent le même tempo : observer, proposer, mesurer, déployer, valider, documenter. Les équipes gagnent ainsi un réflexe de précision, au lieu d’empiler des index par réflexe défensif.
| Étape | Outil/indicateur | Critère de passage |
|---|---|---|
| Identifier | Digests p95/p99, taux d’erreur | Top 3 cibles isolées |
| Concevoir | Règle du préfixe, couverture | Conflits/duplications écartés |
| Tester | EXPLAIN ANALYZE, index invisible | Gain ≥ 20–30 % au p95 |
| Déployer | DDL INSTANT/INPLACE, LOCK=NONE | Impact négligeable en production |
| Élaguer | Usage index, redondance | Suppression confirmée sans régression |
Et la réalité du terrain quand la donnée double tous les six mois ?
La stratégie tient si elle absorbe la croissance : partitions réfléchies, plans stables et statistiques fraîches. L’indexation ne remplace pas le modèle ; elle l’exprime avec efficacité.
Les charges en rafale réclament des index sobres pour ne pas alourdir les écritures. Les tables volumineuses gagnent une partition temporelle lorsque les requêtes collent à cette dimension, à condition de conserver des index utiles par partition. Les statistiques se rafraîchissent régulièrement, les histogrammes gardent l’optimiseur lucide. Les plans critiques se fixent par la conception plus que par des hints ; les hints ne servent qu’à désembourber une urgence. Et lorsque la volumétrie impose une autre architecture (cache matérialisé, CQRS, entrepôt pour l’analytique), l’indexation continue de jouer sa partition sur l’opérationnel, au plus près des gestes quotidiens.
À l’échelle, quelques choix paient longtemps : clés primaires monotones pour limiter la fragmentation, colonnes étroites dans les indexes pour économiser le buffer pool, expressions normalisées (par exemple e‑mail en lower() stocké) pour un matching constant. La base devient prévisible, et la prévisibilité vaut de l’or quand le trafic monte sans prévenir.
Conclusion : l’index comme écriture fine du temps
L’indexation MySQL n’est ni un talisman, ni une punition. C’est une écriture fine du temps : du temps économisé à la lecture, du temps assumé à l’écriture, et du temps gagné dans la sérénité des opérations. Un bon index tranche des détours ; un mauvais index en invente de nouveaux.
La maîtrise repose sur une poignée de gestes sûrs : comprendre les requêtes qui comptent, choisir des structures adaptées, ordonner les colonnes comme une phrase bien scandée, mesurer sans concession, et tailler les excès avant qu’ils ne pèsent. La donnée bouge, les usages se déplacent, et l’index suit, agile. À cette condition, MySQL cesse d’être un coffre-fort pesant pour devenir un instrument qui répond juste, même quand la salle entière se lève en même temps.