MongoDB Indexes
Why Indexes Matter
Without an index, MongoDB performs a collection scan — reading every document to find matches. On large collections this is extremely slow. An index stores a small, ordered subset of data that MongoDB can traverse quickly, similar to a book's index. The trade-off is slightly slower writes and additional storage.
Creating Indexes
// Single field index (1 = ascending, -1 = descending)
db.users.createIndex({ email: 1 })
db.users.createIndex({ age: -1 })
// Compound index — covers queries on multiple fields
db.users.createIndex({ role: 1, age: -1 })
// Unique index — enforces uniqueness
db.users.createIndex({ email: 1 }, { unique: true })
// Unique compound index
db.products.createIndex({ category: 1, sku: 1 }, { unique: true })
// Named index
db.users.createIndex({ email: 1 }, { name: "idx_email_unique", unique: true })
// List all indexes on a collection
db.users.getIndexes()
// Drop a specific index by name
db.users.dropIndex("idx_email_unique")
// Drop all indexes except _id
db.users.dropIndexes()
Multikey, Text, and Geospatial Indexes
// Multikey index — automatically created when indexing an array field
db.products.createIndex({ tags: 1 })
// Now queries like db.products.find({ tags: "mongodb" }) use the index
// Text index — for full-text search
db.articles.createIndex({ title: "text", body: "text" })
// Search using $text
db.articles.find({ $text: { $search: "mongodb tutorial" } })
// With relevance score
db.articles.find(
{ $text: { $search: "mongodb" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
// Geospatial index — for location-based queries
db.places.createIndex({ location: "2dsphere" })
// Find places within 5km of a point
db.places.find({
location: {
$near: {
$geometry: { type: "Point", coordinates: [-73.97, 40.77] },
$maxDistance: 5000
}
}
})
// Hashed index — for hash-based sharding
db.users.createIndex({ userId: "hashed" })
Sparse and TTL Indexes
// Sparse index — only indexes documents that have the field
// Useful for optional fields to save space
db.users.createIndex({ phone: 1 }, { sparse: true })
// TTL index — automatically deletes documents after a time period
// Expire sessions after 1 hour (3600 seconds)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// Expire at a specific date stored in the document
db.events.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })
// Document: { event: "sale", expiresAt: ISODate("2024-12-31T23:59:59Z") }
Analyzing Index Usage with explain()
// Basic explain — shows query plan
db.users.find({ email: "alice@example.com" }).explain()
// executionStats — shows actual execution metrics
db.users.find({ email: "alice@example.com" }).explain("executionStats")
// Key fields to check in explain output:
// winningPlan.stage: "IXSCAN" = index used, "COLLSCAN" = no index (slow!)
// executionStats.totalDocsExamined: should be close to nReturned
// executionStats.executionTimeMillis: query execution time
// Example output snippet:
// {
// "winningPlan": { "stage": "FETCH", "inputStage": { "stage": "IXSCAN", "indexName": "email_1" } },
// "executionStats": { "nReturned": 1, "totalDocsExamined": 1, "executionTimeMillis": 0 }
// }
// Hint — force MongoDB to use a specific index
db.users.find({ email: "alice@example.com" }).hint({ email: 1 })
// Single field: { field: 1 } or { field: -1 }
// Compound: { field1: 1, field2: -1 }
// Multikey: { arrayField: 1 } (auto-detected)
// Text: { field: "text" }
// Geospatial: { location: "2dsphere" } or { location: "2d" }
// Hashed: { field: "hashed" }
// Unique: { field: 1 }, { unique: true }
// Sparse: { field: 1 }, { sparse: true }
// TTL: { dateField: 1 }, { expireAfterSeconds: N }
// Partial: { field: 1 }, { partialFilterExpression: { active: true } }
// Wildcard: { "$**": 1 } (indexes all fields)