Aller au contenu principal
Lance-toi : 40% de réduction sur toutes les formations jusqu'au 30 juin

Comprendre les index : pourquoi ta requête est lente

Il arrive un moment, dans la vie d'une application, où une page qui s'affichait en un quart de seconde commence à en prendre plusieurs. Le code n'a pas bougé, le serveur non plus. Ce qui a changé, c'est la quantité de données en base. Si tu développes, tu as peut-être déjà croisé ce genre de situation. Une table qui comptait quelques centaines de lignes en contient maintenant des centaines de milliers, et tout se met à traîner. Le premier réflexe est souvent de soupçonner l'hébergeur, le framework ou la machine. La cause est pourtant souvent ailleurs. Pour répondre, la base lit la table entière, ligne par ligne, parce que rien ne lui indique où chercher. Plus il y a de lignes, plus ce travail s'allonge. Les index servent à régler ce problème. Cet article explique ce qu'ils font, pourquoi ils peuvent faire passer une requête de plusieurs secondes à quelques millisecondes, et comment les poser au bon endroit sans en abuser.

Guides & tutoriels ·
Adel LATIBI
Adel LATIBI

Le Briefing Dev - les ressources et actus de la semaine, droit dans ta boîte chaque vendredi gratuitement.

Le moment où tout se met à ramer

Prends une table classique, celle des utilisateurs. Tu cherches une personne par son adresse mail, parce qu'elle se connecte :

SELECT * FROM utilisateurs
WHERE email = 'sarah@exemple.fr';

Cette requête a l'air anodine. Pour la base, sans index, elle veut dire ceci : lis la première ligne, compare son email. Pas la bonne. Ligne suivante, compare. Pas la bonne. Et ainsi de suite jusqu'à trouver, ou jusqu'à la fin de la table.

Ce parcours porte un nom : le full table scan, le balayage complet de la table. Avec cent lignes, il est instantané. Avec deux cent mille, la base lit en moyenne cent mille lignes avant de trouver la bonne. Tu ne le vois pas en développement parce que ta base de test est minuscule. Tu le découvres en production, quand les données réelles s'accumulent.

Le piège est que le coût grossit en ligne droite avec tes données. Deux fois plus de lignes, deux fois plus de travail pour la même requête. Multiplie ce coût par le nombre d'utilisateurs qui chargent la page en même temps, et tu obtiens un serveur de base de données qui sature alors que ton code n'a pas changé d'une virgule. C'est le genre de problème qui n'arrive jamais le jour de la mise en ligne, mais trois mois plus tard, sans prévenir.

C'est le piège le plus courant chez les développeurs juniors et les personnes en reconversion. Le code est correct, la requête renvoie le bon résultat, les tests passent. Le souci n'apparaît que sous le poids des données. Une requête lente ignorée fait partie des signaux qu'un projet se dégrade en silence, un sujet abordé dans notre article sur les signaux d'alarme de la dette technique.

Ce qu'un index fait réellement

Prends un livre technique de huit cents pages. Tu veux retrouver toutes les pages où on parle de "transactions". Deux options.

La première : tu lis le livre page par page en notant chaque occurrence. C'est le full table scan. La seconde : tu vas à l'index alphabétique à la fin, tu cherches "transactions", et tu lis directement : pages 142, 387, 601. Tu sautes au bon endroit sans tout parcourir.

Un index de base de données, c'est exactement cet index alphabétique. C'est une structure annexe, triée, qui contient les valeurs d'une colonne et l'emplacement de la ligne correspondante. La base ne lit plus toute la table, elle consulte l'index, trouve la position, et va chercher la ligne directement.

Dans la quasi-totalité des bases relationnelles (MySQL, PostgreSQL, SQLite), cet index prend la forme d'un arbre équilibré, le B-tree. L'idée tient en une image : au lieu de chercher dans une liste à plat, tu descends dans un arbre où chaque embranchement élimine la moitié des possibilités restantes.

            [ M ]
           /     \
      [ F ]       [ S ]
     /    \      /     \
  A..E   G..L  N..R   T..Z
Pour trouver "Sarah", la base compare avec M, part à droite, compare avec S, et atteint la bonne plage en trois sauts au lieu de parcourir toutes les lignes.

Le résultat est spectaculaire. Sur deux cent mille lignes, un balayage complet lit deux cent mille entrées. Un arbre équilibré en lit environ dix-huit. C'est la différence entre un travail qui explose avec tes données et un travail qui reste quasi constant. Voilà pourquoi un index peut transformer huit secondes en quelques millisecondes.

Cette idée est sans doute la plus rentable que tu apprendras sur les bases de données. Elle ne demande pas de maîtriser une théorie complexe. Elle demande de comprendre un seul mécanisme : la base cherche, soit en parcourant tout, soit en suivant un index. Ton travail consiste à lui fournir le bon index au bon endroit, puis à vérifier qu'elle l'utilise. Le reste découle de là.

Créer et vérifier un index

La syntaxe est courte. Pour indexer la colonne email de notre table :

CREATE INDEX idx_utilisateurs_email
ON utilisateurs (email);

Une ligne. Et la requête qui prenait huit secondes répond instantanément. Mais avant de poser un index au hasard, apprends à mesurer. C'est le réflexe qui sépare le bricolage de la méthode.

EXPLAIN, ton meilleur outil de diagnostic

Toutes les bases relationnelles savent te dire comment elles comptent exécuter une requête, avant de l'exécuter. Le mot-clé est EXPLAIN. Tu le places devant ta requête :

EXPLAIN SELECT * FROM utilisateurs
WHERE email = 'sarah@exemple.fr';

Sans index, la sortie affiche un type d'accès ALL sous MySQL, ce qui signifie balayage complet, avec un nombre de lignes examinées proche du total de la table. C'est le drapeau rouge. Après avoir créé l'index, relance la même commande : le type passe à ref ou const, et le nombre de lignes examinées chute à une poignée. Tu as la preuve chiffrée que ton index sert.

Ne saute jamais cette étape. Poser un index sans vérifier avec EXPLAIN, c'est deviner. Mesurer avant et après, c'est savoir. La même logique de vérification s'applique d'ailleurs à ton code applicatif, comme on le voit dans notre guide sur les tests et le TDD.

L'index composite et l'ordre des colonnes

Souvent tu ne filtres pas sur une seule colonne. Imagine une table de commandes où tu cherches les commandes d'un client, triées par date :

SELECT * FROM commandes
WHERE client_id = 42
ORDER BY date_creation DESC;

Ici un index sur deux colonnes, dit composite, est le bon choix :

CREATE INDEX idx_commandes_client_date
ON commandes (client_id, date_creation);

L'ordre des colonnes compte énormément. Un index composite se lit de gauche à droite, comme un annuaire trié d'abord par nom de famille, puis par prénom. Avec un annuaire trié ainsi, tu retrouves vite tous les "Martin", puis le bon prénom parmi eux. Mais si tu ne connais que le prénom, l'annuaire ne te sert à rien.

La règle pratique : mets en première position la colonne sur laquelle tu fais une égalité (client_id = 42), et en seconde celle qui sert au tri ou à une plage. Un index (client_id, date_creation) sert la requête ci-dessus. Un index (date_creation, client_id) la sert beaucoup moins bien.

Index simple ou index unique ?

Il existe une variante que tu vas croiser souvent : l'index unique. En plus d'accélérer la lecture, il interdit deux valeurs identiques dans la colonne. Sur un email, c'est précisément ce que tu veux : la base refusera la création de deux comptes avec la même adresse.

CREATE UNIQUE INDEX idx_utilisateurs_email
ON utilisateurs (email);

Tu gagnes sur deux tableaux. La recherche par email reste rapide, et la règle d'unicité est garantie au niveau de la base, pas seulement dans ton code. C'est une protection que tu ne peux pas oublier d'écrire un jour de fatigue, car la base la fait respecter pour toi à chaque insertion. Beaucoup de doublons en production viennent justement d'une contrainte posée dans le code applicatif mais absente de la base.

Si tout ce vocabulaire est encore flou, c'est le signe qu'il te manque les fondations SQL. Notre formation SQL et bases de données relationnelles avec MySQL couvre ces bases, de l'interrogation à la conception, sur quatre jours.

Les pièges qui annulent tes index

Un index n'est pas magique. Plusieurs erreurs courantes le rendent inutile, ou pire, font de lui un poids mort. Les voici, par ordre de fréquence.

Indexer tout, partout

Un index accélère les lectures mais ralentit les écritures. À chaque INSERT, UPDATE ou DELETE, la base doit aussi mettre à jour chaque index concerné. Sur une table qui reçoit beaucoup d'écritures, dix index inutiles pèsent lourd. Indexe les colonnes que tu interroges souvent dans un WHERE, un JOIN ou un ORDER BY, pas toutes les colonnes par précaution.

Indexer une colonne à faible cardinalité

La cardinalité, c'est le nombre de valeurs distinctes dans une colonne. Une colonne est_actif qui ne vaut que vrai ou faux a une cardinalité de deux. Un index dessus ne sert presque à rien : si la moitié de la table est active, la base lit quand même la moitié des lignes. L'index brille quand il sépare beaucoup de valeurs uniques, comme un email ou un numéro de commande.

Appliquer une fonction sur la colonne indexée

-- L'index sur email est ignoré
WHERE LOWER(email) = 'sarah@exemple.fr'

Dès que tu enveloppes la colonne dans une fonction (LOWER, DATE, une concaténation), la base ne peut plus utiliser l'index, car l'index contient les valeurs brutes, pas leur version transformée. Elle repart en balayage complet. Garde la colonne nue à gauche du signe égal et applique la transformation côté application, ou crée un index adapté à la fonction si ta base le permet.

Le LIKE qui commence par un joker

Une recherche WHERE nom LIKE 'Mar%' peut utiliser l'index, parce qu'elle fixe le début. Mais WHERE nom LIKE '%mar%', avec un joker en tête, oblige la base à tout parcourir. C'est logique : un annuaire trié ne t'aide pas à trouver tous les noms qui contiennent "mar" au milieu. Pour ce besoin, il faut une autre approche, comme la recherche plein texte.

Tu remarqueras que beaucoup de ces requêtes sont générées pour toi par un ORM, dans un projet Symfony ou une API FastAPI. L'ORM écrit le SQL, mais c'est à toi de décider quels index poser. Comprendre ce qui se passe sous le capot reste ta responsabilité de développeur backend.

Oublier de retirer les index morts

Les besoins évoluent. Une requête disparaît, son index reste. Il continue de ralentir tes écritures et d'occuper de l'espace sans rien apporter. Fais le ménage de temps en temps : la plupart des bases savent te dire quels index ne sont jamais utilisés.

Retiens une chose simple. L'index n'est pas une option à activer pour aller plus vite partout. C'est un outil ciblé : tu observes une requête lente, tu confirmes le balayage complet avec EXPLAIN, tu poses l'index qui correspond au filtre, tu vérifies le gain. Trois index bien choisis valent mieux que vingt posés à l'aveugle. Et le jour où une requête redevient lente, tu sauras exactement quoi regarder en premier.

Questions fréquentes

Faut-il créer un index sur chaque colonne par sécurité ?

Non. Chaque index ralentit les écritures et occupe de l'espace. Indexe seulement les colonnes que tu utilises souvent dans un WHERE, un JOIN ou un ORDER BY. Pour les autres, l'index est un coût sans bénéfice.

Comment savoir si une requête lente vient d'un index manquant ?

Place EXPLAIN devant ta requête. Si le plan affiche un balayage complet (type ALL sous MySQL) et un grand nombre de lignes examinées, un index sur la colonne filtrée résoudra le problème dans la majorité des cas.

La clé primaire est-elle déjà indexée ?

Oui. La clé primaire est indexée automatiquement par la base. C'est pour cette raison qu'une recherche par id reste rapide même sur des millions de lignes. Tu n'as rien à faire pour elle.

Pourquoi mon index n'est-il pas utilisé alors qu'il existe ?

Les causes les plus fréquentes : tu appliques une fonction sur la colonne (comme LOWER), tu utilises un LIKE qui commence par un joker, ou la colonne a trop peu de valeurs distinctes. EXPLAIN te confirme si l'index est réellement employé.

L'ordre des colonnes dans un index composite a-t-il une importance ?

Oui, beaucoup. Un index composite se lit de gauche à droite. Place d'abord la colonne sur laquelle tu fais une égalité, puis celle qui sert au tri ou à une plage de valeurs. Un ordre inversé peut rendre l'index inutilisable pour ta requête.

Un index ralentit-il les écritures ?

Oui. À chaque insertion, mise à jour ou suppression, la base met à jour les index concernés. Sur une table à fort volume d'écritures, multiplier les index dégrade les performances. C'est le compromis à doser entre lectures rapides et écritures rapides.

Vous êtes expert ?

Partagez votre expertise sur notre blog

Tutoriel, retour d'expérience, analyse - publiez un article invité et gagnez en visibilité.

Écrire pour nous