Trois ou quatre requêtes. C’est en général tout ce qui sépare une application réactive d’une application qu’on dit « lente ». Pas la base entière, pas le serveur sous-dimensionné : une poignée de requêtes précises qui tirent le reste vers le fond.
« La base est lente. » On me sort cette phrase à chaque mission performance, et elle est presque toujours fausse. Le boulot consiste à nommer les vrais coupables avant de toucher à quoi que ce soit.
La plupart des lenteurs viennent d’une poignée de requêtes mal optimisées, pas de la base entière : on les sort du journal des requêtes lentes, on lit leur plan avec EXPLAIN ANALYZE, et neuf fois sur dix un index bien posé règle l’affaire.
Étape 1 : capturer les requêtes lentes
J’active le journal des requêtes lentes avec un seuil raisonnable, 200 ms en général, et je laisse tourner en conditions réelles. Pas sur mon poste avec dix lignes en base, ça ne dit rien. Sous le trafic du jour, le vrai.
Vient ensuite l’agrégation, et c’est là que les gens se trompent de coupable. Une requête à 2 secondes appelée une fois par jour, on s’en moque. Celle à 40 ms qu’on rejoue douze mille fois par heure, en revanche, elle vous mange le serveur. Je classe par temps cumulé. Le temps unitaire ne sert qu’à départager.
Étape 2 : lire le plan avec EXPLAIN
Pour chaque coupable, EXPLAIN ANALYZE raconte ce que le moteur fait réellement : le balayage complet d’une table là où un index manquait, un tri qui déborde sur le disque, une jointure prise dans le mauvais ordre. Fini l’intuition, on a la mécanique sous les yeux. Le mot à traquer dans la sortie, c’est Seq Scan sur une grosse table. Quand il s’accompagne d’un filtre, vous tenez votre réponse neuf fois sur dix.
Étape 3 : corriger, souvent par un index
La cause la plus fréquente, et de loin, c’est l’absence d’index sur une colonne qu’on filtre ou qu’on joint. Un index bien placé transforme un balayage de plusieurs millions de lignes en accès direct. Le gain se compte en ordres de grandeur, rien de marginal.
Reste qu’un index se paie à l’écriture : chaque INSERT et chaque UPDATE doit le maintenir. Je l’ajoute donc là où il sert. Couvrir toutes les colonnes « au cas où » alourdit les écritures pour rien et finit par desservir des lectures qui n’en demandaient pas tant.
D’autres corrections classiques suivent quand l’index ne suffit pas : réécrire une sous-requête corrélée qui se rejoue ligne par ligne, virer un SELECT * qui traîne quinze colonnes dont on lit deux, paginer un résultat qui ramène cinquante mille lignes pour en afficher vingt.
L’erreur que je vois le plus
Optimiser au jugé. L’an dernier, une équipe avait passé trois semaines à réécrire une couche de cache parce que « le listing produit était lent ». Quatre secondes la page. Le journal a parlé en dix minutes : une seule jointure non indexée, rejouée en boucle, qui pesait le plus clair du temps. Un index, et la page est tombée sous 500 ms. Trois semaines de cache pour rien, là où une demi-journée de mesure pointait droit sur la cause.
Sans le journal et sans EXPLAIN, on répare ce qui tenait debout, et on rentre chez soi convaincu d’avoir bien travaillé.
Où la méthode s’arrête
Soyons honnête sur ses limites. Cette séquence trouve les requêtes lentes ; elle ne corrige pas un schéma de données mal conçu dès le départ, ni un besoin métier qui exige réellement de brasser des millions de lignes à chaque appel. Il arrive aussi qu’après index, réécriture et pagination, une requête reste lente parce qu’elle demande un travail incompressible. À ce stade, on ne parle plus d’optimisation SQL mais d’architecture : dénormaliser, précalculer, mettre en cache pour de bon. Le journal vous dit où chercher, pas jusqu’où vous pourrez descendre. Ce qui compte au final, c’est l’écart de temps mesuré avant/après, jamais la satisfaction d’avoir bidouillé.