索引滥用与优化建议
索引滥用与优化建议
索引是MongoDB中提升查询性能的关键机制,但不当使用会导致写入性能下降、存储空间浪费等问题。合理设计索引需要平衡查询效率与资源消耗,避免常见误区。
索引滥用的常见表现
过度索引
在同一个集合上创建过多索引是典型问题。每个索引都会占用存储空间,并在写入时产生额外开销。例如用户集合存在以下索引:
// 用户集合的冗余索引示例
db.users.createIndex({ username: 1 }) // 唯一索引
db.users.createIndex({ username: 1, age: 1 }) // 包含前导字段的复合索引
第二个复合索引的前导字段已包含在第一个索引中,此时查询{username: 'john'}
会同时命中两个索引,造成索引选择器开销。
低效复合索引
复合索引字段顺序不当会导致索引失效:
// 低效的复合索引顺序
db.orders.createIndex({ status: 1, created_at: 1 })
// 以下查询无法充分利用索引
db.orders.find({ created_at: { $gt: ISODate('2023-01-01') } })
应该将高选择性的字段放在前面:
// 优化后的索引顺序
db.orders.createIndex({ created_at: 1, status: 1 })
索引覆盖不足
未覆盖常用查询的所有字段时,会导致内存排序:
// 产品查询示例
db.products.find(
{ category: 'electronics', price: { $lt: 1000 } },
{ name: 1, price: 1 }
).sort({ rating: -1 })
// 现有索引
db.products.createIndex({ category: 1, price: 1 })
此时需要添加排序字段到索引:
db.products.createIndex({
category: 1,
price: 1,
rating: -1
})
索引优化实践方案
索引选择性分析
通过$indexStats
评估索引使用情况:
db.collection.aggregate([{ $indexStats: {} }])
输出示例显示命中次数和内存占用:
{
"name" : "category_1_price_1",
"accesses" : {
"ops" : NumberLong(2543),
"since" : ISODate("2023-06-01T00:00:00Z")
}
}
查询模式识别
使用explain()
分析查询执行计划:
db.orders.find({
user_id: ObjectId("507f1f77bcf86cd799439011"),
status: "completed"
}).explain("executionStats")
重点关注:
totalKeysExamined
:索引扫描文档数totalDocsExamined
:集合扫描文档数executionTimeMillis
:执行耗时
索引合并策略
对于OR查询,考虑$or
表达式的索引合并:
// 原始查询
db.articles.find({
$or: [
{ tags: "mongodb" },
{ view_count: { $gt: 10000 } }
]
})
// 优化方案
db.articles.createIndex({ tags: 1 })
db.articles.createIndex({ view_count: -1 })
MongoDB会自动执行索引合并(INDEX_MERGE),但需注意每个分支必须有对应索引。
特殊场景索引策略
时间序列数据
针对时间序列数据的特殊优化:
// 时间序列集合创建
db.createCollection("sensor_data", {
timeseries: {
timeField: "timestamp",
metaField: "sensor_id",
granularity: "hours"
}
})
// 优化查询的索引
db.sensor_data.createIndex({
"metadata.sensor_type": 1,
timestamp: -1
})
全文检索优化
结合文本索引与过滤条件:
// 创建文本索引
db.reviews.createIndex({
comments: "text",
product_id: 1
})
// 高效查询
db.reviews.find({
$text: { $search: "battery life" },
product_id: 12345
})
多键索引陷阱
数组字段索引的注意事项:
// 可能产生索引爆炸的示例
db.products.createIndex({ tags: 1 })
// 插入包含大数组的文档
db.products.insertOne({
name: "UltraBook",
tags: ["laptop", "ultraportable", "windows", "i7", "16gb", "ssd"...]
})
每个数组元素都会创建索引条目,导致索引体积急剧膨胀。
索引维护与监控
索引重建策略
使用collMod
重建索引减少碎片:
db.runCommand({
collMod: "orders",
index: {
keyPattern: { created_at: 1 },
hidden: true // 先隐藏再重建
}
})
性能基准测试
使用benchRun
进行负载测试:
var ops = [
{
op: "find",
ns: "test.orders",
query: { status: "pending" }
}
]
db.adminCommand({ benchRun: ops, parallel: 10, seconds: 30 })
存储引擎调优
调整WiredTiger缓存大小:
# mongod.conf配置
storage:
wiredTiger:
engineConfig:
cacheSizeGB: 8 # 建议为物理内存的50-60%
索引设计模式进阶
部分索引
只为满足条件的文档创建索引:
// 只为活跃用户创建索引
db.users.createIndex(
{ last_login: -1 },
{ partialFilterExpression: { status: "active" } }
)
哈希分片键索引
分片集群中的特殊考虑:
// 哈希分片键索引
sh.shardCollection("analytics.events", { _id: "hashed" })
// 范围查询需要额外索引
db.events.createIndex({ created_at: -1 })
生存时间索引
自动过期数据的实现:
// 会话数据24小时后过期
db.sessions.createIndex(
{ last_accessed: 1 },
{ expireAfterSeconds: 86400 }
)
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:数据建模常见误区
下一篇:分片键选择的最佳实践