Appearance
MongoDB 索引优化
索引是MongoDB性能优化的关键组件,它们能够显著提高查询性能。本章详细介绍MongoDB索引的类型、创建、使用和优化策略。
索引基础概念
索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或字段集的值,并按字段值排序,从而允许高效地执行相等匹配和范围查询。
索引的工作原理
javascript
// 无索引时的查询
db.users.find({ email: "user@example.com" })
// MongoDB需要扫描整个集合
// 有索引时的查询
db.users.createIndex({ email: 1 })
db.users.find({ email: "user@example.com" })
// MongoDB使用索引快速定位文档
索引类型
单字段索引
javascript
// 创建单字段升序索引
db.users.createIndex({ email: 1 })
// 创建单字段降序索引
db.users.createIndex({ createdAt: -1 })
// 单字段索引支持的查询
db.users.find({ email: "user@example.com" }) // 高效
db.users.find({ email: { $gt: "a" } }) // 高效
db.users.find({ email: { $in: ["a", "b"] } }) // 高效
复合索引
javascript
// 创建复合索引
db.users.createIndex({ status: 1, age: -1 })
// 复合索引支持的查询
db.users.find({ status: "active" }) // 高效
db.users.find({ status: "active", age: { $gte: 18 } }) // 高效
db.users.find({ status: "active", age: 25 }) // 高效
// 不支持的查询(前缀不匹配)
db.users.find({ age: 25 }) // 无法使用索引
多键索引
javascript
// 为数组字段创建索引
db.users.createIndex({ tags: 1 })
// 查询数组字段
db.users.find({ tags: "developer" }) // 高效
db.users.find({ tags: { $in: ["developer", "manager"] } }) // 高效
文本索引
javascript
// 创建文本索引
db.articles.createIndex({ content: "text", title: "text" })
// 或者创建通配符文本索引
db.articles.createIndex({ "$**": "text" })
// 文本搜索查询
db.articles.find({ $text: { $search: "mongodb database" } })
// 文本搜索带权重
db.articles.createIndex({
title: "text",
content: "text"
}, {
weights: {
title: 10,
content: 5
}
})
地理空间索引
javascript
// 2dsphere索引(用于球面几何)
db.places.createIndex({ location: "2dsphere" })
// 2d索引(用于平面几何)
db.places.createIndex({ location: "2d" })
// 地理空间查询
db.places.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [116.397428, 39.90923]
},
$maxDistance: 1000
}
}
})
哈希索引
javascript
// 创建哈希索引(仅支持精确匹配)
db.users.createIndex({ userId: "hashed" })
// 哈希索引只支持相等匹配
db.users.find({ userId: 12345 }) // 高效
// 不支持范围查询
索引管理
创建索引
javascript
// 基本索引创建
db.users.createIndex({ email: 1 })
// 创建唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// 创建稀疏索引
db.users.createIndex({ phoneNumber: 1 }, { sparse: true })
// 创建TTL索引(自动删除过期文档)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// 后台创建索引(避免阻塞数据库)
db.users.createIndex({ profile: 1 }, { background: true })
查看索引信息
javascript
// 查看集合的所有索引
db.users.getIndexes()
// 查看索引大小
db.users.totalIndexSize()
// 查看索引统计信息
db.users.aggregate([{ $indexStats: {} }])
删除索引
javascript
// 删除特定索引
db.users.dropIndex({ email: 1 })
// 删除所有索引(保留_id索引)
db.users.dropIndexes()
// 通过索引名称删除
db.users.dropIndex("email_1")
索引优化策略
选择合适的索引
javascript
// 查询模式分析
// 如果经常查询status和age
db.users.find({ status: "active", age: { $gte: 18 } })
// 应该创建以下索引
db.users.createIndex({ status: 1, age: 1 })
// 而不是
db.users.createIndex({ age: 1, status: 1 }) // 效率较低
复合索引字段顺序
javascript
// 等值查询字段应放在前面
db.users.createIndex({ status: 1, age: 1, createdAt: -1 })
// 范围查询字段应放在后面
db.users.find({
status: "active", // 等值
age: { $gte: 18 }, // 范围
createdAt: { $gte: new Date("2023-01-01") } // 范围
})
覆盖查询优化
javascript
// 索引
db.users.createIndex({ status: 1, name: 1 })
// 覆盖查询(只使用索引,不访问文档)
db.users.find(
{ status: "active" },
{ name: 1, status: 1, _id: 0 } // 只返回索引字段
)
索引性能分析
使用explain()分析查询
javascript
// 分析查询执行计划
db.users.find({ email: "user@example.com" }).explain("executionStats")
// 重要指标:
// - stage: "IXSCAN" 表示使用索引扫描
// - stage: "COLLSCAN" 表示全表扫描
// - totalDocsExamined: 检查的文档数
// - totalDocsReturned: 返回的文档数
// - executionTimeMillis: 执行时间
索引使用情况分析
javascript
// 查看索引使用统计
db.aggregate([
{ $indexStats: {} }
])
// 查找未使用的索引
db.setProfilingLevel(2, { slowms: 0 })
// 运行应用程序一段时间后...
db.system.profile.distinct("ns").forEach(function(ns) {
if (ns.startsWith("your_database")) {
printjson(db.runCommand("aggregate", ns.split(".")[1], {
pipeline: [{$indexStats: {}}]
}))
}
})
高级索引技术
部分索引
javascript
// 只为满足条件的文档创建索引
db.users.createIndex(
{ accountBalance: 1 },
{
partialFilterExpression: {
accountStatus: "active"
}
}
)
// 只为特定值的文档创建索引
db.events.createIndex(
{ timestamp: 1 },
{
partialFilterExpression: {
status: { $ne: "deleted" }
}
}
)
稀疏索引
javascript
// 只为有该字段的文档创建索引
db.users.createIndex(
{ phoneNumber: 1 },
{ sparse: true }
)
// 稀疏唯一索引
db.users.createIndex(
{ phoneNumber: 1 },
{
unique: true,
sparse: true
}
)
数组字段索引
javascript
// 多键索引
db.users.createIndex({ tags: 1 })
// 为嵌套文档数组创建索引
db.users.createIndex({ "hobbies.name": 1 })
// 为数组元素创建索引
db.users.createIndex({ "grades.grade": 1 })
索引最佳实践
索引创建策略
javascript
// 1. 分析查询模式
// 记录应用程序中常用的查询
// 2. 优先创建高选择性字段的索引
// 选择性 = 不同值的数量 / 总文档数
db.users.distinct("status").length / db.users.count() // 低选择性
db.users.distinct("email").length / db.users.count() // 高选择性
// 3. 避免过度索引
// 每个索引都会增加写操作的开销
索引监控
javascript
// 监控索引使用情况
db.currentOp({
"ns": "your_database.users",
"waitingForLock": true
})
// 查看索引命中率
db.serverStatus().indexCounters
索引维护
javascript
// 重建索引(在后台)
db.users.reIndex()
// 压缩索引
db.runCommand({
compact: "users",
indexes: ["email_1", "status_age_1"]
})
索引性能优化案例
案例1:用户查询优化
javascript
// 问题:用户列表查询很慢
db.users.find({
status: "active",
age: { $gte: 18, $lte: 65 },
city: "北京"
})
// 解决方案:创建复合索引
db.users.createIndex({
status: 1,
city: 1,
age: 1
})
案例2:分页查询优化
javascript
// 问题:深度分页性能差
db.users.find().skip(10000).limit(10) // 性能差
// 解决方案:使用范围查询
// 首先按_id排序
db.users.createIndex({ createdAt: 1, _id: 1 })
// 然后使用范围查询
const lastId = ObjectId("...") // 上一页最后一个文档的_id
db.users.find({
createdAt: { $gte: lastCreatedAt },
_id: { $gt: lastId }
}).sort({ createdAt: 1, _id: 1 }).limit(10)
案例3:文本搜索优化
javascript
// 创建优化的文本索引
db.articles.createIndex(
{
title: "text",
content: "text",
tags: "text"
},
{
weights: {
title: 10,
content: 5,
tags: 3
},
name: "text_index",
default_language: "chinese", // 如果有中文内容
language_override: "language"
}
)
// 使用文本搜索
db.articles.find(
{ $text: { $search: "mongodb performance" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
索引陷阱和注意事项
1. 复合索引字段顺序
javascript
// 错误:范围查询字段在前
db.users.createIndex({ age: 1, status: 1 }) // 低效
db.users.find({ age: { $gte: 18 }, status: "active" })
// 正确:等值查询字段在前
db.users.createIndex({ status: 1, age: 1 }) // 高效
2. 索引过多
javascript
// 避免为每个查询创建索引
// 考虑索引的使用频率和写操作开销
3. 索引大小
javascript
// 监控索引大小
db.users.stats().indexSizes
// 确保索引能够放入内存
总结
MongoDB索引优化是一个持续的过程,需要根据应用程序的查询模式不断调整。合理的索引策略可以显著提高查询性能,但过多的索引会影响写性能。在设计索引时,应该:
- 分析查询模式,优先为高频查询创建索引
- 使用复合索引时注意字段顺序
- 定期监控索引使用情况和性能
- 使用部分索引和稀疏索引来减少索引大小
- 避免过度索引,平衡读写性能
通过遵循这些最佳实践,可以构建高性能的MongoDB应用程序。