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) = 2026empê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é surclient_id. - Un
LIKEqui 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ù ».