Skip to Content
DatabasesIndexation

Indexation — Stratégies de performance

Choisir et créer les bons index est souvent plus efficace qu’optimiser une requête.

Commandes de base

MySQL / MariaDB

-- Créer un index simple CREATE INDEX idx_cmd_date ON commandes(cree_a); -- Créer un index composite (ordre important) CREATE INDEX idx_cmd_user_date ON commandes(utilisateur_id, cree_a); -- Lister les index d'une table SHOW INDEX FROM commandes; -- Supprimer un index ALTER TABLE commandes DROP INDEX idx_cmd_date; -- Vérifier l'utilisation d'un index par EXPLAIN EXPLAIN SELECT * FROM commandes WHERE utilisateur_id = 42 AND cree_a > '2024-01-01';

PostgreSQL

-- Créer un index CREATE INDEX idx_cmd_date ON commandes(cree_a); -- Index Composite CREATE INDEX idx_cmd_user_date ON commandes(utilisateur_id, cree_a); -- Lister les index SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'commandes'; -- Supprimer un index DROP INDEX idx_cmd_date; -- Analyser l'exécution EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT * FROM commandes WHERE utilisateur_id = 42;

SQLite

CREATE INDEX idx_cmd_date ON commandes(cree_a); PRAGMA index_list('commandes'); EXPLAIN QUERY PLAN SELECT * FROM commandes WHERE cree_a > '2024-01-01';

Règles de conception

Ordre des colonnes en index composite

L’ordre compte. Suivre la règle equality first, then range :

-- ✅ Bon : equality sur utilisateur_id, range sur cree_a CREATE INDEX idx ON commandes(utilisateur_id, cree_a); -- ❌ Mauvais : la range sur cree_a coupe l'utilisation de utilisateur_id CREATE INDEX idx ON commandes(cree_a, utilisateur_id);

Pour une requête WHERE utilisateur_id = X AND cree_a > 'date', l’index cmd(user_id, cree_a) est efficace car MySQL/PostgreSQL peut utiliser les deux colonnes.

Index B-tree vs autres types

TypeUsageExemple
B-tree (défaut)Requêtes générales, tri, rangeWHERE prix > 10
HASHÉgalité uniquementWHERE id = 42
GIN (Postgres)JSONB, full-text, tableauxWHERE jsonb @> '{"key":"val"}'
GIST (Postgres)Géospatiaux, opérateurs customPostGIS
ExpressionIndexer le résultat d’une fonctionCREATE INDEX idx_lower ON t(LOWER(col))

Index UNIQUE

-- Protège contre les doublons et accélère les recherches CREATE UNIQUE INDEX idx_cmd_code ON commandes(code_commande);

Ajouter UNIQUE quand la sémantique métier l’exige (email, code interne). Ça évite les bugs plus qu’il n’accélère.

Quand un index ralentit

Un index a un coût : chaque INSERT/UPDATE/DELETE doit mettre à jour tous les index.

Vérifier l’impact

-- MySQL : table information_schema SELECT index_name, cardinality, seq_in_index, column_name FROM information_schema.statistics WHERE table_schema = 'mon_bdd' AND table_name = 'commandes' ORDER BY index_name, seq_in_index;

Indices inutilisés

-- PostgreSQL : vérifier les index jamais scannés SELECT indexrelname, idx_scan FROM pg_stat_user_indexes ORDER BY idx_scan ASC;
-- MySQL : activer la surveillance SET GLOBAL stat_user_indexes=ON; -- Then check performance_schema

Supprimer un index avec zéro scan depuis plus de 30 jours en production si la charge d’écriture est significative.

EXPLAIN — Lire un plan d’exécution

Indicateurs clés

CritèreSignification
type: ALLFull table scan — besoin d’un index
type: rangeUtilisation d’un index avec plage
type: refÉgalité via index (bon)
type: eq_refÉgalité via clé primaire (idéal)
rows: élevéBeaucoup de lignes examinées
Extra: Using filesortTrie en mémoire — index inadéquat
Extra: Using temporaryTable temporaire — jointure/groupage coûteux

Exemple

EXPLAIN SELECT * FROM commandes WHERE utilisateur_id = 42 AND cree_a > '2024-01-01'; type: range key: idx_cmd_user_date rows: 15 Extra: Using index condition

15 lignes examinées vs des milliers en full scan = l’index fonctionne.

Index covering (covered queries)

Un index covering (ou index only scan) évite de lire les lignes de la table en retournant directement les valeurs depuis l’index. C’est souvent le gain de performance le plus important pour les requêtes de lecture.

Détection dans EXPLAIN

EXPLAIN SELECT nom, prenom FROM utilisateurs WHERE ville = 'Paris'; type: index key: idx_ville_nom_prenom Extra: Using index

Le Extra: Using index confirme que l’index couvre la requête. Pas de lecture de lignes supplémentaires, pas de désérialisation.

Exemple avec BUFFERS

EXPLAIN (ANALYZE, BUFFERS) SELECT nom, prenom FROM utilisateurs WHERE ville = 'Paris'; -> Seq Scan on utilisateurs (cost=0.00..45.00 rows=120 width=40) (actual time=0.012..0.890 rows=120 loops=1) Filter: (ville = 'Paris') Rows Removed by Filter: 9880 Buffers: shared hit=100 dirtied=11

Interprétation

ChampSignification
actual timeTemps réel entre le premier row et le dernier row
rows (actual)Nombre de lignes réellement extraites (pas estimé)
loopsNombre de passages au niveau parent
Buffers: shared hit=100100 pages satisfaites depuis le cache OS
Buffers: shared read=5050 pages lues depuis le disque (coûteux)

Objectif : maximiser hit et minimiser read. Un index covering réduit drastiquement les lectures disque.

Concevoir un index covering

-- Requête courante SELECT nom, prenom FROM utilisateurs WHERE ville = 'Paris' ORDER BY nom; -- Index covering adapté CREATE INDEX idx_u_ville_nom_prenom ON utilisateurs(ville, nom, prenom);

Règles :

  • Toutes les colonnes de la WHERE en premier (égalité d’abord, puis range)
  • Toutes les colonnes de la SELECT ensuite (ordre croissant pour le ORDER BY)
  • Éviter SELECT * — un index covering ne fonctionne que si toutes les colonnes récupérées sont dans l’index.

Plus l’index est large, plus il occupe de mémoire et ralentit les écritures. Un index covering de 5 colonnes reste acceptable pour une table à forte lecture. Au-delà, peser le coût d’écriture contre le gain de lecture.

Pièges courants

PiègeSymptômeSolution
COLLATE/string utf8mb4Index inefficace sur chaînesVérifier le COLLATE dans EXPLAIN
Fonction dans WHEREWHERE YEAR(date) = 2024 bypassUtiliser une plage : WHERE date >= '2024-01-01' AND date < '2025-01-01'
Trop d’indexINSERT lentAuditer les scans, supprimer les zéro-utilisation
Index sur colonnes low-cardinalityIndex peu utile (BOOL, ENUM petits)Privilégier les colonnes à haute cardinalité
ANALYZE nécessairePlan d’exécution périméLancer ANALYZE TABLE après gros chargements

L’index adapté vaut mieux que le code optimisé.