Optimisation des requêtes SQL : lire les plans d’exécution et corriger les requêtes lentes, avec des délais

  1. Accueil
  2. Blog
  3. Bases de données et SQL

Bases de données et SQL

Optimisation des requêtes SQL : Lire les plans d’exécution et corriger les requêtes lentes, avec des délais

Arrêtez de deviner pourquoi une requête est lente. Apprenez à lire un plan d’exécution, puis appliquez les correctifs les plus importants, avec des mesures avant et après issues d’un tableau réel d’un million de lignes.

9 min de lecture
Optimisation des requêtes SQL : lire les plans d'exécution et corriger les requêtes lentes, avec des timings

À retenir

  • Commencez par le plan d’exécution. Il vous indique ce que fait réellement la base de données.
  • Recherchez des analyses complètes sur de grandes tables, puis ajoutez des index qui correspondent à vos filtres et jointures.
  • Évitez d’encapsuler une colonne indexée dans une fonction, car cela peut empêcher une utilisation efficace de l’index.
  • Mesurez avant et après, car chaque base de données et chaque ensemble de données se comportent différemment.

Une requête qui prenait quelques millisecondes l’année dernière peut prendre quelques minutes aujourd’hui, simplement parce que la table s’agrandit. L’optimisation des requêtes SQL relève moins d’astuces astucieuses que d’un processus reproductible : découvrez ce que fait la base de données, supprimez le travail inutile et vérifiez que cela a aidé.

Nous avons testé chaque technique ci-dessous sur un million de lignes générées dans SQLite 3.50. Les timings sont le meilleur de plusieurs exécutions à partir d’un seul ordinateur portable, en utilisant une base de données en mémoire. Ils ne correspondront pas à votre système et PostgreSQL, MySQL et SQL Server ont chacun leurs propres optimiseurs et formats de plan. Concentrez-vous sur l’ampleur de chaque amélioration et sur les principes qui la sous-tendent.

Les résultats en un coup d’oeil

Technique Avant Après
Index sur la colonne de filtre 73 ms 0,01 ms
Plage plutôt qu’une fonction sur une colonne 96 ms 33 ms
Indice composite pour deux filtres 19 ms 2,2 ms
Jeu de clés plutôt qu’un décalage profond 8,1 ms 0,01 ms

La configuration des tests

CREATE TABLE customers (
  id INTEGER PRIMARY KEY, name TEXT, city TEXT
);
CREATE TABLE orders (
  id INTEGER PRIMARY KEY, customer_id INT, status TEXT,
  created TEXT, total INT, note TEXT
);
-- 50,000 customers and 1,000,000 orders with random values

Un processus d’optimisation simple

  1. Trouvez les requêtes qui comptent. Utilisez des journaux de requêtes lents ou une surveillance. Une requête de 200 ms exécutée mille fois par minute peut avoir plus d’importance qu’un rapport mensuel de 10 secondes.
  2. Lisez le plan. Voyez ce que fait réellement la base de données.
  3. Modifiez une chose. Ajoutez un index ou réécrivez une partie de la requête.
  4. Mesurez à nouveau sur des données réalistes. Un plan sur 100 lignes de test vous en dit très peu sur un million.
  5. À surveiller au fil du temps. Les plans peuvent changer à mesure que les données augmentent et que les statistiques évoluent.

Lire un plan d’exécution

Un plan d’exécution est la stratégie étape par étape de la base de données pour exécuter une requête, y compris les index qu’elle utilise et l’ordre dans lequel elle rejoint les tables. Préfixez une requête pour la voir :

Base de données Commande
SQLite EXPLIQUER LA SÉLECTION DU PLAN DE REQUÊTE...
PostgreSQL EXPLIQUER ANALYSER SÉLECTIONNER...
MySQL EXPLAIN SELECT ... ou EXPLAIN ANALYZE
Serveur SQL Plan d’exécution réel dans SSMS

Notez que EXPLAIN ANALYZE dans PostgreSQL exécute réellement la requête, alors soyez prudent lorsque vous l’utilisez avec UPDATE ou DELETE. Voici ce qu’il faut rechercher en premier :

Tu vois Signification Habituellement
Scan ou Scan séquentiel Lit tout le tableau Un souci sur les grandes tables
Recherche ou recherche d’index Passe aux lignes correspondantes Bon
Indice de couverture Réponse à partir de l’index seul Très bien
Arbre B temporaire ou tri Travail de tri supplémentaire Ça vaut le coup de vérifier

Une analyse complète n’est pas automatiquement mauvaise. Si une requête nécessite la majeure partie d’une petite table, l’analyse peut être le choix le plus rapide. Dans PostgreSQL EXPLAIN ANALYZE, comparez également les lignes estimées avec les lignes réelles. Une grande différence signifie souvent que les statistiques des tableaux sont obsolètes.

Ajoutez le bon index

Cette requête recherche les commandes d’un client :

SELECT id, total FROM orders WHERE customer_id = 123;

Avant d’ajouter un index, le plan était une analyse complète :

SCAN orders
-- 20 rows in 73 ms

Ensuite nous avons ajouté un index sur la colonne de filtre :

CREATE INDEX idx_orders_customer ON orders(customer_id);

SEARCH orders USING INDEX idx_orders_customer (customer_id=?)
-- 20 rows in 0.01 ms

Les mêmes 20 lignes sont revenues des milliers de fois plus rapidement, car la base de données ne lisait plus un million de lignes pour les retrouver. Aucune autre technique présentée dans cet article ne nous a permis de gagner autant. Les bons premiers candidats sont les colonnes utilisées dans WHERE, JOIN et ORDER BY, surtout lorsqu’elles réduisent les résultats à quelques lignes. Notre guide des index SQL explique leur fonctionnement.

Ne pas envelopper les colonnes indexées dans des fonctions

Voici deux façons de trouver les commandes de mars, avec un index sur created :

-- Function on the column
SELECT id FROM orders WHERE substr(created, 1, 7) = '2026-03';
-- SCAN orders USING COVERING INDEX idx_orders_created
-- 84,961 rows in 96 ms

-- Plain range
SELECT id FROM orders
WHERE created >= '2026-03-01' AND created < '2026-04-01';
-- SEARCH orders USING COVERING INDEX idx_orders_created (created>? AND created<?)
-- 84,961 rows in 33 ms

Les deux renvoient des lignes identiques, mais la première a pris environ trois fois plus de temps. Le plan montre pourquoi : avec une fonction sur la colonne, la base de données ne peut pas accéder à une position dans l’index trié, elle doit donc SCAN parcourir chaque entrée. La version Range peut SEARCH directement au bon endroit. Une condition qui peut utiliser un index de cette manière est appelée sargable.

Le même problème apparaît avec ces modèles courants :

Éviter Préférer
OÙ ANNÉE (créée) = 2026 OÙ créé >= '2026-01-01' ET créé < '2027-01-01'
OÙ prix * 2 > 100 OÙ prix > 50
OÙ INFÉRIEUR(e-mail) = 'a@b.com' Un index basé sur une fonction ou un classement insensible à la casse
OÙ le nom COMME '%99' Une recherche de préfixe ou une recherche en texte intégral

Nous avons également vérifié ce dernier. Un caractère générique de début est produit SCAN customers, car un index trié à partir de la première lettre ne peut pas aider à trouver une correspondance à la fin.

Index composites

Lorsqu’une requête filtre sur deux colonnes, un index à une seule colonne ne fait qu’une partie du travail :

SELECT id FROM orders
WHERE status = 'paid'
  AND created >= '2026-03-01' AND created < '2026-03-08';

-- Using only the created index: 6,198 rows in 19 ms

CREATE INDEX idx_status_created ON orders(status, created);

-- SEARCH orders USING COVERING INDEX idx_status_created
--   (status=? AND created>? AND created<?)
-- 6,198 rows in 2.2 ms

L’ordre des colonnes est très important. Un index sur (status, created) fonctionne comme un annuaire téléphonique trié par nom puis prénom. Vous pouvez rechercher un nom de famille seul, mais pas un prénom seul. Notre requête sur status seule pourrait utiliser cet index. Le filtrage sur created seul ne pouvait généralement pas l’utiliser efficacement. En règle générale, placez les colonnes testées avec égalité en premier et les colonnes de plage après elles.

Ce plan indique également couvrant l’index, ce qui signifie que l’index contenait tout ce dont la requête avait besoin, de sorte que la table elle-même n’avait pas besoin d’être lue. Sélectionner uniquement les colonnes dont vous avez besoin rend cela plus probable, ce qui est une autre raison à éviter SELECT *.

Pagination plus rapide

Utiliser OFFSET pour les pages profondes représente un coût caché, puisque la base de données doit toujours parcourir toutes les lignes ignorées :

-- Page deep into the results
SELECT id FROM orders ORDER BY id LIMIT 20 OFFSET 500000;
-- 8.1 ms

-- Keyset pagination: continue from the last id seen
SELECT id FROM orders WHERE id > 500000 ORDER BY id LIMIT 20;
-- 0.01 ms

La pagination du jeu de touches reste rapide, quelle que soit la profondeur à laquelle vous allez. Cela évite également le déplacement des lignes entre les pages lorsque de nouvelles données sont ajoutées. Le compromis est que les utilisateurs ne peuvent pas accéder directement à la page 5 000, ce qui constitue rarement un réel besoin. Sur les tableaux avec des lignes larges ou avec plus de lignes à sauter, l’écart peut être beaucoup plus important qu’il ne l’était lors de notre test.

Conseil : Nos timings mesurent une requête à la fois sur une machine inactive. Sous une charge réelle, une requête inefficace fait plus de dégâts, car elle détient également des verrous et entre en concurrence pour la mémoire, le disque et le processeur avec tout le reste.

Écrivez des requêtes plus simples

  • Sélectionnez uniquement les colonnes dont vous avez besoin. SELECT * envoie plus de données et peut empêcher un index de couvrir la requête.
  • Filtrer tôt. Utilisez WHERE pour supprimer les lignes avant le regroupement, et HAVING uniquement pour les conditions sur les agrégats. Voir SQL GROUP BY et HAVING.
  • Utilisez LIMIT lorsque vous n’avez besoin que de quelques lignes.
  • Préférez UNION ALL lorsque vous n’avez pas besoin de supprimer les doublons. Notre test UNION vs UNION ALL montre ce que cela permet d’économiser.
  • Évitez les DISTINCT inutiles. Il s’agit souvent de masquer une jointure qui produit des doublons.
  • Utilisez EXISTS pour les vérifications d’existence. Cela peut s’arrêter à la première correspondance, comme l’explique notre guide des sous-requêtes SQL.
  • Surveillez les requêtes N+1. Une requête par ligne dans une boucle, généralement provoquée par le code de l’application, est souvent le véritable problème. Une seule requête de jointure ou par lots peut remplacer des centaines d’allers-retours.

Rejoint

Indexez les colonnes sur lesquelles vous rejoignez. Nous avons réalisé un rapport sur les dépenses totales des clients d’une ville, et le plan correspondait exactement à ce que vous espériez :

SELECT c.name, SUM(o.total)
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE c.city = 'Goa'
GROUP BY c.id;

-- SCAN c
-- SEARCH o USING COVERING INDEX idx_cover (customer_id=?)
-- 9,900 rows in 34 ms

Il scanne les clients, puis utilise un index pour retrouver les commandes de chacun. Sans index sur orders.customer_id, la base de données devrait faire beaucoup plus de travail pour chaque client. Un index sur la clé étrangère est l’un des ajouts les plus courants et les plus précieux que vous puissiez apporter. Pour rafraîchir votre compréhension des bases, consultez Explication des jointures SQL.

Quand les index vous coûtent cher

Les index ne sont pas gratuits, n’en ajoutez donc pas un pour chaque colonne :

  • Écritures plus lentes. Chaque insertion, mise à jour et suppression doit également mettre à jour chaque index.
  • Plus de stockage et de mémoire. Les index peuvent croître en taille sur les grandes tables.
  • Peu d’avantages sur les colonnes non sélectives. Un index sur une colonne vrai ou faux peut ne pas aider si la plupart des lignes correspondent.
  • Redondance. Un index sur (a) est souvent inutile si vous en avez déjà un sur (a, b).

Vérifiez périodiquement les statistiques d’utilisation et supprimez les index que rien n’utilise. Et gardez les statistiques à jour avec ANALYZE ou l’équivalent de votre base de données, car l’optimiseur s’appuie sur elles pour choisir un bon plan. Parfois, la partie la plus lente est la contention plutôt que la requête. Des transactions de longue durée détenant des verrous peuvent donner l’impression qu’une requête rapide semble lente.

Erreurs courantes

  • Optimiser sans mesurer. De nombreuses suppositions sûres s’avèrent fausses.
  • Tests avec des données minuscules. Les plans et les horaires changent considérablement à mesure que les tables s’agrandissent.
  • Faire confiance à une astuce pour une autre base de données. Les conseils pour un moteur peuvent ne pas s’appliquer à un autre.
  • Ajout d’index aveuglément. Chacun ralentit les écritures.
  • Ignorer l’effet de première exécution. Les caches accélèrent une deuxième exécution, donc exécutez-les plusieurs fois.

Pour conclure

La plupart des requêtes lentes ont des causes ordinaires : un index manquant, une fonction sur une colonne ou trop de données récupérées. Prenez votre requête réelle la plus lente cette semaine, exécutez EXPLAIN dessus et recherchez une analyse complète sur une grande table. Réparez une chose, mesurez à nouveau et notez ce que vous avez modifié.

Questions fréquentes

Qu’est-ce que l’optimisation des requêtes SQL ?

Il s’agit du processus consistant à faire en sorte que les requêtes utilisent moins de temps et moins de ressources, généralement en examinant les plans d’exécution, en ajoutant des index appropriés et en réécrivant les requêtes inefficaces.

Qu’est-ce qu’un plan d’exécution ?

Il s’agit de la stratégie choisie par la base de données pour exécuter une requête, y compris la manière dont elle lit les tables, les index qu’elle utilise et l’ordre dans lequel elle rejoint.

Comment trouver des requêtes lentes ?

Activez le journal des requêtes lentes de votre base de données ou utilisez des outils tels que pg_stat_statements dans PostgreSQL. Concentrez-vous ensuite sur les requêtes dont la durée totale est la plus élevée.

L’ajout d’un index accélère-t-il toujours les requêtes ?

Non. Cela facilite les lectures sélectives, mais ralentit les écritures et utilise le stockage. L’optimiseur peut également l’ignorer si la plupart des lignes correspondent à la condition.

SELECT * est-il mauvais pour les performances ?

C’est possible. Il transfère les données inutiles et peut empêcher un index de couvrir la requête. Il est généralement préférable de répertorier uniquement les colonnes dont vous avez besoin.

Continuez à apprendre

Travailler avec Stack Quora

Besoin d’aide pour les tests, WordPress ou le référencement ?

Nous aidons les équipes à proposer de meilleurs logiciels et à développer leurs sites. Dites-nous sur quoi vous travaillez et nous vous proposerons une prochaine étape claire.

Continuez à lire

Plus de guides sur tests, IA et développement moderne

Vous avez apprécié cet article ? Choisissez un sujet et accédez directement à nos nouveaux didacticiels.

Intelligence artificielle

Les hallucinations de l'IA expliquées : pourquoi les modèles de langage inventent les choses et comment les réduire

Les hallucinations de l'IA ont expliqué : ce qu'elles sont, pourquoi les modèles de langage les produisent, les types courants, comment les réduire …

Lire l'article →
DevOps

Variables d'environnement expliquées : fichiers .env, secrets et configuration correctement effectués

Variables d'environnement expliquées avec des exemples Python que nous avons exécutés : lecture et définition, pourquoi chaque valeur est une chaîne…

Lire l'article →
Développement logiciel

Limitation du débit de l'API expliquée : algorithmes, en-têtes et gestion des erreurs 429

Limitation du débit de l'API expliquée avec une simulation que nous avons exécutée : compartiment de jetons, fenêtre fixe, fenêtre glissante, 429 ré…

Lire l'article →
Automatisation des tests

Se moquer en Python : unittest.mock, patch et les erreurs qui font mentir les tests

Moquerie en Python avec unittest.mock, testée : Mock et return_value, side_effect, correctifs où un nom est utilisé, spec et autospec, dates moqueus…

Lire l'article →
Tests logiciels

Couverture du code expliquée : couverture des lignes par rapport aux branches et ce que signifie réellement 100 %

Couverture du code expliquée avec une véritable exécution de cover.py : couverture des lignes, des branches et des conditions, lecture d'un rapport,…

Lire l'article →
Bases de données et SQL

DELETE vs TRUNCATE vs DROP dans SQL : différences, restauration et quand les utiliser

DELETE vs TRUNCATE vs DROP expliqué avec SQL testé : ce que chacun supprime, restauration, déclencheurs, réinitialisation d'identité, vitesse et en …

Lire l'article →
Bases de données et SQL

Vues SQL expliquées : créez, utilisez et évitez les pièges, avec des exemples

Vues SQL expliquées avec les requêtes que nous avons exécutées : création d'une vue, résultats en direct, masquage des colonnes, performances, vues …

Lire l'article →
Développement logiciel

Interface TypeScript vs Type : différences réelles, testées avec le compilateur

Interface TypeScript vs type, vérifiée avec le vrai compilateur : ce que les deux peuvent faire, ce qu'un seul peut faire, la fusion des déclaration…

Lire l'article →
Développement logiciel

Python *args et **kwargs expliqués : fonctions flexibles avec des exemples clairs

Python *args et **kwargs expliqués avec le code que nous avons exécuté : emballage et déballage, ordre des paramètres, arguments de mot-clé uniqueme…

Lire l'article →
Développement logiciel

Fonctions Python Lambda : syntaxe, utilisations réelles et quand ne pas les utiliser

Fonctions Python lambda expliquées avec le code que nous avons exécuté : syntaxe, tri avec clé, carte et filtre, fermetures, piège de liaison tardiv…

Lire l'article →
Intelligence artificielle

Que sont les agents IA ? Comment ils fonctionnent, avec une simple boucle d'agent que vous pouvez exécuter

Que sont les agents IA ? Un guide simple sur la boucle d'agent, les outils, la mémoire et les garde-fous, avec une petite boucle Python que nous avo…

Lire l'article →
DevOps

Surveillance vs observabilité : la vraie différence, avec les journaux, les métriques et les traces

Surveillance et observabilité expliquées : échecs connus et inconnus, journaux, métriques et traces, présentation pas à pas des incidents de paiemen…

Lire l'article →
DevOps

Volumes Docker vs montages Bind : conserver les données du conteneur de la bonne manière

Volumes Docker et montages de liaison expliqués : pourquoi les données du conteneur disparaissent, volumes nommés, montages de liaison, tmpfs, exemp…

Lire l'article →
Automatisation des tests

TDD vs BDD : principales différences, une fonctionnalité conçue dans les deux sens et quand les utiliser

TDD vs BDD expliqué avec une fonctionnalité de remise construite dans les deux sens : refactor rouge-vert avec le code que nous avons exécuté, scéna…

Lire l'article →
Tests logiciels

Tests d'acceptation utilisateur (UAT) : processus, liste de contrôle et exemples réels

Les tests d'acceptation des utilisateurs sont expliqués : qui les exécute, en quoi ils diffèrent du contrôle qualité, un processus en sept étapes, u…

Lire l'article →
Bases de données et SQL

Explication des valeurs SQL NULL : IS NULL, COALESCE, NULLIF et les pièges à éviter

Les valeurs SQL NULL expliquées avec les requêtes que nous avons exécutées : logique à trois valeurs, IS NULL, COALESCE, NULLIF, comment les agrégat…

Lire l'article →
Bases de données et SQL

Instruction SQL CASE WHEN : syntaxe, exemples réels et erreurs courantes

SQL CASE WHEN expliqué avec les requêtes que nous avons exécutées : formulaires simples et recherchés, ordre des conditions, pivots avec SUM, astuce…

Lire l'article →
Développement logiciel

Qu'est-ce que JWT ? Les jetons Web JSON expliqués avec un exemple fonctionnel

Qu’est-ce que JWT ? JSON Web Tokens expliqué : les trois parties, un jeton construit et falsifié dans le code que nous avons exécuté, JWT vs session…

Lire l'article →
Développement logiciel

Boucle d'événements JavaScript expliquée : pile d'appels, microtâches et minuteries avec des exemples

La boucle d'événements JavaScript expliquée avec le code que nous avons exécuté : pile d'appels, files d'attente de tâches et de microtâches, pourqu…

Lire l'article →
Développement logiciel

Générateurs Python et rendement expliqués : économisez de la mémoire avec des exemples réels

Les générateurs Python et le rendement expliqués avec le code que nous avons exécuté : comment ils s'arrêtent et reprennent, les économies de mémoir…

Lire l'article →
Intelligence artificielle

Que sont les intégrations ? Vecteurs et similarité cosinus expliqués simplement

Que sont les intégrations ? Un guide en langage simple sur les vecteurs, la similarité cosinus et la recherche sémantique, avec un petit exemple que…

Lire l'article →
DevOps

Déploiement Bleu-Vert vs Canary : différences, compromis et comment choisir

Explication du déploiement bleu-vert ou canari : comment chacun fonctionne, coût, risque et restauration, regroupement d'utilisateurs stable que nou…

Lire l'article →
Automatisation des tests

Tests instables : quelles sont leurs causes et comment les corriger définitivement

Des tests irréguliers expliqués avec des démos que nous avons exécutées : les principales causes, comment en confirmer et en diagnostiquer une, les …

Lire l'article →
Tests logiciels

Tests de charge vs tests de contrainte : différences, métriques et exemples k6

Explication des tests de charge et des tests de stress : ce que chacun prouve, les tests de pointe et de trempage, les métriques qui comptent, pourq…

Lire l'article →
Tests logiciels

Scénario de test et scénario de test : principales différences, exemples et quand les utiliser

Scénario de test et scénario de test expliqués : des définitions claires, un tableau côte à côte, une fonctionnalité de réinitialisation de mot de p…

Lire l'article →
Bases de données et SQL

Optimisation des requêtes SQL : lire les plans d'exécution et corriger les requêtes lentes, avec des délais

Optimisation des requêtes SQL testée sur un million de lignes : lire les plans d'exécution, ajouter les bons index, corriger les filtres non Sargabl…

Lire l'article →
Bases de données et SQL

SQL UNION vs UNION ALL : différences, exemples et performances testées

SQL UNION vs UNION ALL expliqué avec les requêtes que nous avons exécutées : des doublons, les règles suivent toutes les deux, un véritable bug de d…

Lire l'article →
Développement logiciel

Tutoriel API JavaScript Fetch : GET, POST, gestion des erreurs et délais d'attente

Un didacticiel de l'API JavaScript Fetch testé sur un serveur local : GET et POST, pourquoi 404 ne lance pas, un assistant réutilisable, des chaînes…

Lire l'article →
Développement logiciel

Gestion des fichiers Python : lecture, écriture, CSV, JSON et pathlib avec exemples

Gestion des fichiers Python expliquée avec le code que nous avons exécuté : modes ouverts, avec lecture de fichiers volumineux, CSV, JSON, pathlib, …

Lire l'article →
Développement logiciel

Git merge vs rebase : différences, exemples et quand utiliser chacun

Git merge vs rebase expliqué avec de vraies sorties de log, un tableau comparatif, la règle d'or du rebase et un flux de travail sûr pour les équipes.

Lire l'article →
Développement logiciel

List comprehension Python : syntaxe, exemples et cas à éviter

La list comprehension Python expliquée : syntaxe, filtres, if-else, boucles imbriquées, temps mesurés et cas où une boucle for classique est préféra…

Lire l'article →
Bases de données et SQL

Jointures SQL expliquées avec exemples (Inner, Left, Right, Full)

Comprenez les jointures SQL pas à pas avec des tables et requêtes d'exemple : INNER, LEFT, RIGHT, FULL, CROSS et SELF, plus erreurs courantes et con…

Lire l'article →