第五章:索引优化¶
索引概述¶
索引是 MongoDB 提高查询性能的关键机制。没有索引,MongoDB 必须扫描整个集合来匹配查询条件。
索引类型¶
创建索引¶
单字段索引¶
// 创建升序索引
db.users.createIndex({ name: 1 })
// 创建降序索引
db.users.createIndex({ created_at: -1 })
// 后台创建(不阻塞操作)
db.users.createIndex({ email: 1 }, { background: true })
// 指定名称
db.users.createIndex({ name: 1 }, { name: "idx_name" })
复合索引¶
// 复合索引
db.orders.createIndex({ user_id: 1, created_at: -1 })
// 索引顺序很重要
// 支持查询:
// - user_id
// - user_id + created_at
// 不支持:
// - created_at(单独查询)
多键索引(数组索引)¶
文本索引¶
// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" })
// 文本搜索
db.articles.find({ $text: { $search: "MongoDB 教程" } })
// 带权重
db.articles.createIndex(
{ title: "text", content: "text" },
{ weights: { title: 10, content: 5 } }
)
// 查看相关性分数
db.articles.find(
{ $text: { $search: "MongoDB" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
地理空间索引¶
// 2dsphere 索引
db.places.createIndex({ location: "2dsphere" })
// 附近查询
db.places.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [116.4074, 39.9042] // 北京
},
$maxDistance: 5000 // 5km
}
}
})
// 范围查询
db.places.find({
location: {
$geoWithin: {
$centerSphere: [
[116.4074, 39.9042],
5 / 6378.1 // 5km 弧度
]
}
}
})
哈希索引¶
通配符索引¶
// 通配符索引(MongoDB 4.2+)
db.products.createIndex({ "attributes.$**": 1 })
// 支持查询任意嵌套字段
db.products.find({ "attributes.color": "red" })
部分索引¶
// 只索引满足条件的文档
db.orders.createIndex(
{ status: 1, created_at: -1 },
{ partialFilterExpression: { status: "completed" } }
)
// 只索引存在的字段
db.users.createIndex(
{ email: 1 },
{ partialFilterExpression: { email: { $exists: true } } }
)
稀疏索引¶
TTL 索引¶
// 自动过期删除
db.sessions.createIndex(
{ created_at: 1 },
{ expireAfterSeconds: 3600 } // 1 小时后删除
)
// 指定过期时间
db.logs.createIndex(
{ expireAt: 1 },
{ expireAfterSeconds: 0 } // 在 expireAt 时间删除
)
唯一索引¶
// 唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// 复合唯一索引
db.orders.createIndex({ user_id: 1, order_no: 1 }, { unique: true })
索引属性¶
db.collection.createIndex(
{ field: 1 },
{
name: "index_name", // 索引名称
unique: true, // 唯一性
sparse: true, // 稀疏索引
background: true, // 后台创建
expireAfterSeconds: 3600, // TTL
partialFilterExpression: {...}, // 部分索引条件
weights: {...}, // 文本索引权重
default_language: "english", // 文本索引语言
collation: { locale: "zh" } // 排序规则
}
)
索引管理¶
查看索引¶
// 查看所有索引
db.users.getIndexes()
// 查看索引大小
db.users.totalIndexSize()
// 查看索引详情
db.users.aggregate([
{ $indexStats: {} }
])
删除索引¶
// 删除指定索引
db.users.dropIndex("idx_name")
// 删除指定字段索引
db.users.dropIndex({ name: 1 })
// 删除所有索引(保留 _id)
db.users.dropIndexes()
重建索引¶
查询计划分析¶
explain()¶
// 基本分析
db.users.find({ name: "张三" }).explain()
// 详细分析
db.users.find({ name: "张三" }).explain("executionStats")
// 全部分析
db.users.find({ name: "张三" }).explain("allPlansExecution")
执行计划字段¶
{
"queryPlanner": {
"plannerVersion": 1,
"namespace": "mydb.users",
"indexFilterSet": false,
"parsedQuery": { "name": { "$eq": "张三" } },
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN", // 索引扫描
"keyPattern": { "name": 1 },
"indexName": "name_1"
}
},
"rejectedPlans": []
},
"executionStats": {
"executionSuccess": true,
"nReturned": 1, // 返回文档数
"executionTimeMillis": 0, // 执行时间
"totalKeysExamined": 1, // 扫描索引键数
"totalDocsExamined": 1, // 扫描文档数
"indexUsed": "name_1"
}
}
扫描类型¶
| stage | 说明 |
|---|---|
| COLLSCAN | 全表扫描(无索引) |
| IXSCAN | 索引扫描 |
| FETCH | 根据索引获取文档 |
| SORT | 内存排序 |
| SORT_KEY_GENERATOR | 排序键生成 |
优化指标¶
// 理想情况
totalKeysExamined ≈ nReturned
totalDocsExamined ≈ nReturned
// 需要优化
totalDocsExamined >> nReturned // 扫描太多文档
索引优化策略¶
ESR 规则¶
索引字段顺序:Equality → Sort → Range
// 查询
db.orders.find({ user_id: "u001" })
.sort({ created_at: -1 })
.skip(0).limit(10)
// 最优索引
db.orders.createIndex({ user_id: 1, created_at: -1 })
// E: user_id (等值)
// S: created_at (排序)
// R: 无范围查询
覆盖索引¶
// 创建覆盖索引
db.users.createIndex({ name: 1, email: 1 })
// 覆盖查询(不需要回表)
db.users.find(
{ name: "张三" },
{ _id: 0, name: 1, email: 1 } // 只返回索引字段
)
// 验证
db.users.find({ name: "张三" }, { _id: 0, name: 1, email: 1 })
.explain("executionStats")
// winningPlan.stage 应为 "PROJECTION" 而非 "FETCH"
避免内存排序¶
// 查询需要排序
db.orders.find({ status: "completed" }).sort({ created_at: -1 })
// 好的索引(支持排序)
db.orders.createIndex({ status: 1, created_at: -1 })
// 差的索引(需要内存排序)
db.orders.createIndex({ status: 1 })
// explain 会显示 SORT stage
索引交集¶
// MongoDB 可以使用多个索引
db.orders.createIndex({ user_id: 1 })
db.orders.createIndex({ status: 1 })
// 查询可能使用索引交集
db.orders.find({ user_id: "u001", status: "completed" })
// 但复合索引通常更高效
db.orders.createIndex({ user_id: 1, status: 1 })
索引使用建议¶
适合创建索引的场景¶
- 高频查询字段
- 排序字段
- 连接字段($lookup)
- 范围查询字段
避免创建索引的场景¶
- 低选择性字段(如性别、状态只有几个值)
- 写入频繁、查询少的集合
- 大数组字段(多键索引占用大)
索引数量建议¶
索引监控¶
索引使用统计¶
db.users.aggregate([
{ $indexStats: {} },
{
$project: {
name: 1,
accesses: 1,
"accesses.ops": 1,
"accesses.since": 1
}
}
])
未使用索引检测¶
// 查找访问次数为 0 的索引
db.users.aggregate([
{ $indexStats: {} },
{ $match: { "accesses.ops": 0 } },
{ $project: { name: 1 } }
])
慢查询分析¶
// 开启 Profiler
db.setProfilingLevel(1, { slowms: 50 })
// 查看慢查询
db.system.profile.find().sort({ ts: -1 }).limit(10)
// 分析慢查询
db.system.profile.find({ millis: { $gt: 100 } })
小结¶
本章学习了:
- ✅ 索引类型(单字段、复合、文本、地理空间等)
- ✅ 索引属性(唯一、稀疏、TTL、部分索引)
- ✅ 查询计划分析
- ✅ 索引优化策略(ESR 规则、覆盖索引)
- ✅ 索引监控
下一章¶
第六章:Python 集成 - 学习使用 Python 操作 MongoDB。