Module 3 : Indexation
Pourquoi les index ?
Les index améliorent les performances des requêtes :
+----------------------------------------------------------+
| SANS INDEX |
+----------------------------------------------------------+
| Collection Scan (COLLSCAN) |
| - Parcourt TOUS les documents |
| - O(n) - Linéaire |
| - Lent sur grandes collections |
+----------------------------------------------------------+
+----------------------------------------------------------+
| AVEC INDEX |
+----------------------------------------------------------+
| Index Scan (IXSCAN) |
| - Structure B-tree |
| - O(log n) - Logarithmique |
| - Rapide même sur millions de docs |
+----------------------------------------------------------+
Index par défaut
// MongoDB cree automatiquement un index sur _id
{
"v": 2,
"key": { "_id": 1 },
"name": "_id_"
}
// Voir les index d'une collection
db.users.getIndexes()
Types d'index
Index simple (Single Field)
// Créer un index sur un champ
db.users.createIndex({ email: 1 }) // Ascendant
db.users.createIndex({ age: -1 }) // Descendant
// Avec options
db.users.createIndex(
{ email: 1 },
{
unique: true, // Valeurs uniques
name: "idx_email", // Nom personnalisé
background: true // Création en arrière-plan
}
)
// Index sur champ imbriqué
db.users.createIndex({ "adresse.ville": 1 })
Index composé (Compound)
// Index sur plusieurs champs
db.users.createIndex({ ville: 1, age: -1 })
// Ordre des champs important !
// Cet index supporte:
// - find({ ville: "Paris" })
// - find({ ville: "Paris", age: 30 })
// - find({ ville: "Paris" }).sort({ age: -1 })
// Mais PAS:
// - find({ age: 30 }) // ville doit être en premier
Règle ESR (Equality, Sort, Range) :
- Equality - Champs avec égalité en premier
- Sort - Champs de tri ensuite
- Range - Champs avec range ($gt, $lt) en dernier
// Exemple ESR
// Requête: ville = "Paris", age > 25, tri par nom
db.users.createIndex({ ville: 1, nom: 1, age: 1 })
// E S R
Index multikey (tableaux)
// Index sur un champ tableau
db.users.createIndex({ competences: 1 })
// Document
{ nom: "Dupont", competences: ["Python", "MongoDB", "Azure"] }
// MongoDB crée une entrée d'index pour chaque élément
// Requête optimisée:
db.users.find({ competences: "Python" })
Index texte (Full-text search)
// Créer un index texte
db.articles.createIndex({ titre: "text", contenu: "text" })
// Avec poids
db.articles.createIndex(
{ titre: "text", contenu: "text" },
{ weights: { titre: 10, contenu: 1 } }
)
// Recherche texte
db.articles.find({ $text: { $search: "mongodb database" } })
// Avec score de pertinence
db.articles.find(
{ $text: { $search: "mongodb" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
// Recherche exacte (phrase)
db.articles.find({ $text: { $search: "\"base de donnees\"" } })
// Exclure un mot
db.articles.find({ $text: { $search: "mongodb -sql" } })
Index geospatial
// Index 2dsphere (coordonnées GPS)
db.locations.createIndex({ position: "2dsphere" })
// Document avec GeoJSON
{
nom: "Tour Eiffel",
position: {
type: "Point",
coordinates: [2.2945, 48.8584] // [longitude, latitude]
}
}
// Recherche à proximité
db.locations.find({
position: {
$near: {
$geometry: {
type: "Point",
coordinates: [2.3522, 48.8566] // Paris centre
},
$maxDistance: 5000 // 5km
}
}
})
// Recherche dans une zone
db.locations.find({
position: {
$geoWithin: {
$centerSphere: [[2.3522, 48.8566], 10 / 6378.1] // 10km
}
}
})
Index TTL (Time-To-Live)
// Suppression automatique après un délai
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 } // 1 heure
)
// Document
{
sessionId: "abc123",
userId: ObjectId("..."),
createdAt: new Date() // Doit être une date
}
// Le document sera supprimé 1h après createdAt
Index partiel
// Index sur un sous-ensemble de documents
db.users.createIndex(
{ email: 1 },
{
partialFilterExpression: {
actif: true
}
}
)
// Index utilisé seulement pour:
db.users.find({ email: "...", actif: true })
// Pas pour:
db.users.find({ email: "..." }) // actif non spécifié
Index sparse
// Index uniquement sur documents ou le champ existe
db.users.createIndex(
{ telephone: 1 },
{ sparse: true }
)
// Documents sans "telephone" ne sont pas indexés
Analyser les performances
// explain() - Plan d'exécution
db.users.find({ ville: "Paris" }).explain()
db.users.find({ ville: "Paris" }).explain("executionStats")
// Résultats clés
{
"queryPlanner": {
"winningPlan": {
"stage": "IXSCAN", // Bon! (vs COLLSCAN)
"indexName": "ville_1"
}
},
"executionStats": {
"totalDocsExamined": 50, // Documents examinés
"totalKeysExamined": 50, // Entrées d'index
"executionTimeMillis": 2 // Temps d'exécution
}
}
// Objectif: totalDocsExamined proche de nReturned
Gestion des index
// Lister les index
db.users.getIndexes()
// Statistiques d'utilisation
db.users.aggregate([{ $indexStats: {} }])
// Supprimer un index
db.users.dropIndex("email_1")
db.users.dropIndex({ email: 1 })
// Supprimer tous les index (sauf _id)
db.users.dropIndexes()
// Reconstruire les index
db.users.reIndex()
Covered Queries
Covered Query = requête où tous les champs demandés sont dans l'index. MongoDB n'a pas besoin de lire les documents.
// Index
db.users.createIndex({ email: 1, nom: 1 })
// Covered query (projection = champs de l'index)
db.users.find(
{ email: "dupont@test.com" },
{ _id: 0, email: 1, nom: 1 }
)
// explain montre:
// "totalDocsExamined": 0 // Aucun document lu!
Bonnes pratiques
- Indexer les champs fréquemment requêtés
- Suivre la règle ESR pour les index composés
- Éviter trop d'index (ralentit les écritures)
- Utiliser
explain()pour valider - Surveiller les index inutilisés
- Préférer les index partiels si applicable
À éviter :
- Index sur champs à forte cardinalité (ObjectId, timestamps) sauf si nécessaire
- Trop d'index composés avec de nombreux champs
- Index multikey sur plusieurs tableaux dans le même document