Index SQL : pourquoi votre requête est lente (et comment le vérifier)

Sur une table de quelques milliers de lignes, tout est rapide. À 500 000 commandes, la page « Mes commandes » met soudain près d'une seconde à s'afficher. Avant d'ajouter du cache, commençons par demander à la base comment elle exécute la requête.

Le point de départ

CREATE TABLE commandes (
  id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  client_id   INT UNSIGNED NOT NULL,
  statut      VARCHAR(20) NOT NULL,
  montant     DECIMAL(10, 2) NOT NULL,
  cree_le     DATETIME NOT NULL
);

-- La requête de la page « Mes commandes »
SELECT id, montant, statut
FROM commandes
WHERE client_id = 42
  AND cree_le >= '2026-01-01'
ORDER BY cree_le DESC
LIMIT 20;

Lire le plan avec EXPLAIN

Il suffit de préfixer la requête par EXPLAIN. Sous MariaDB ou MySQL, on obtient :

+------+-------------+-----------+------+---------------+------+------+--------+-----------------------------+
| id   | select_type | table     | type | possible_keys | key  | ref  | rows   | Extra                       |
+------+-------------+-----------+------+---------------+------+------+--------+-----------------------------+
|    1 | SIMPLE      | commandes | ALL  | NULL          | NULL | NULL | 498213 | Using where; Using filesort |
+------+-------------+-----------+------+---------------+------+------+--------+-----------------------------+

type = ALL signifie un parcours complet de la table : près de 500 000 lignes lues pour en garder vingt, puis un tri en mémoire (Using filesort).

Un index composite, dans le bon ordre

La règle à retenir : d'abord les colonnes filtrées par égalité, ensuite la colonne de plage ou de tri. Un index (client_id, cree_le) range les commandes par client puis par date, exactement ce dont la requête a besoin :

CREATE INDEX idx_commandes_client_date ON commandes (client_id, cree_le);

EXPLAIN SELECT id, montant, statut
FROM commandes
WHERE client_id = 42 AND cree_le >= '2026-01-01'
ORDER BY cree_le DESC
LIMIT 20;
-- type: range | key: idx_commandes_client_date | rows: 37 | Extra: Using where

Plus de parcours complet ni de tri : la base lit directement les 20 lignes dans l'ordre de l'index. Sur cet exemple, on passe de ~800 ms à ~2 ms.

Les pièges qui rendent l'index inutile

  • Une fonction sur la colonne : WHERE YEAR(cree_le) = 2026 empêche d'utiliser l'index. Écrivez plutôt une plage : cree_le >= '2026-01-01' AND cree_le < '2027-01-01'.
  • Le mauvais ordre de colonnes : un index (cree_le, client_id) sert mal un filtre d'égalité sur client_id.
  • Un LIKE qui commence par % : LIKE '%dupont' ne peut pas exploiter un index B-tree.
  • Une conversion de type implicite : comparer une colonne VARCHAR à un nombre force une conversion ligne par ligne.

Chaque index accélère les lectures mais ralentit un peu les écritures et occupe de la place. On indexe les requêtes réellement exécutées, pas « au cas où ».