外观
MongoDB 索引维护与监控
索引维护的重要性
- 保持索引性能:定期维护可以防止索引性能下降
- 减少存储空间:清理索引碎片可以释放存储空间
- 提高查询效率:优化的索引可以加速查询操作
- 降低写操作开销:高效的索引可以减少写操作的延迟
- 预防索引相关故障:提前发现并解决索引问题
索引碎片管理
索引碎片产生的原因
- 频繁的插入操作:导致索引页分裂
- 频繁的删除操作:留下空的索引空间
- 更新操作:可能导致索引键的移动
- 不连续的索引键:导致索引页填充不均匀
检测索引碎片
javascript
// 查看集合的索引统计信息
db.collection.stats().indexDetails
// 查看索引的大小和使用情况
const stats = db.collection.stats()
for (let indexName in stats.indexSizes) {
print(`${indexName}: ${stats.indexSizes[indexName]} bytes`)
}
// 使用 db.currentOp() 查看索引构建情况
db.currentOp({
$or: [
{ "msg": /Index build/ },
{ "cmd.createIndexes": { $exists: true } }
]
})清理索引碎片
- 重建索引
javascript
// 重建单个索引
db.collection.reIndex("index_name")
// 重建所有索引
db.collection.reIndex()- 删除并重建索引
javascript
// 删除索引
db.collection.dropIndex("index_name")
// 重建索引
db.collection.createIndex({ field: 1 }, { name: "index_name" })- 使用 compact 命令
javascript
// 压缩集合(包括索引)
db.runCommand({ compact: "collection_name" })索引重建最佳实践
- 在低峰期进行:避免影响生产环境性能
- 逐步重建:不要同时重建所有索引
- 监控重建进度:使用 db.currentOp() 查看进度
- 备份数据:在重建索引前备份重要数据
- 测试性能:重建前后测试查询性能
索引监控
索引使用统计
javascript
// 启用查询分析器
db.setProfilingLevel(1, { slowms: 100 })
// 查看索引使用情况
db.system.profile.find({
op: "query",
"executionStats.executionStages.inputStage.stage": "IXSCAN"
}).limit(10)
// 查看全表扫描情况
db.system.profile.find({
op: "query",
"executionStats.executionStages.inputStage.stage": "COLLSCAN"
}).limit(10)索引性能指标
- 索引命中率:索引被使用的频率
- 索引扫描数量:每次查询扫描的索引条目数
- 索引大小:索引占用的存储空间
- 索引更新次数:索引被更新的频率
- 索引构建时间:创建或重建索引所需的时间
监控工具
- MongoDB Atlas:提供索引使用情况的可视化监控
- MongoDB Ops Manager:企业版监控工具,支持索引性能监控
- Prometheus + Grafana:通过 MongoDB 导出器监控索引指标
- Datadog:提供索引性能的监控和告警
- New Relic:支持 MongoDB 索引监控
自定义监控脚本
javascript
// 创建索引监控脚本
function monitorIndexes() {
const collections = db.getCollectionNames()
for (let coll of collections) {
print(`\n=== 集合 ${coll} 索引监控 ===`)
const collStats = db.getCollection(coll).stats()
// 查看索引大小
print("索引大小:")
for (let indexName in collStats.indexSizes) {
const sizeMB = collStats.indexSizes[indexName] / (1024 * 1024)
print(` ${indexName}: ${sizeMB.toFixed(2)} MB`)
}
// 查看索引使用情况(需要启用 profiling)
const indexUsage = db.system.profile.aggregate([
{ $match: { ns: `${db.getName()}.${coll}`, op: "query" } },
{ $group: {
_id: "$planSummary",
count: { $sum: 1 },
avgTime: { $avg: "$millis" }
}
},
{ $sort: { count: -1 } }
])
print("\n索引使用情况:")
indexUsage.forEach(usage => {
print(` ${usage._id}: ${usage.count} 次查询,平均耗时 ${usage.avgTime.toFixed(2)} ms`)
})
}
}
// 运行监控脚本
monitorIndexes()索引使用分析
识别未使用的索引
javascript
// 查看索引使用统计
db.adminCommand({ serverStatus: 1 }).indexCounters
// 使用 $indexStats 查看索引使用情况
db.collection.aggregate([
{ $indexStats: {} }
])
// 查找未使用的索引(需要启用 profiling)
const indexes = db.collection.getIndexes().map(idx => idx.name)
const usedIndexes = new Set()
// 收集使用过的索引
db.system.profile.find({
op: { $in: ["query", "getMore"] },
"executionStats.executionStages.inputStage.stage": "IXSCAN"
}).forEach(doc => {
const indexName = doc.executionStats.executionStages.inputStage.keyPattern
? Object.keys(doc.executionStats.executionStages.inputStage.keyPattern).join("_")
: ""
usedIndexes.add(indexName)
})
// 找出未使用的索引
const unusedIndexes = indexes.filter(idx => !usedIndexes.has(idx) && idx !== "_id_")
print("未使用的索引:", unusedIndexes)识别低效索引
javascript
// 查看低效索引(扫描大量文档但返回少量结果)
db.system.profile.find({
op: "query",
"executionStats.executionStages.inputStage.stage": "IXSCAN",
$expr: {
$gt: [
"$executionStats.totalKeysExamined",
{ $multiply: ["$executionStats.nReturned", 10] }
]
}
}).sort({ "executionStats.totalKeysExamined": -1 })索引优化策略
定期审查索引
- 每季度审查一次:检查索引使用情况
- 删除未使用的索引:减少存储空间和写操作开销
- 合并低效索引:将多个单字段索引合并为复合索引
- 优化复合索引顺序:根据查询模式调整字段顺序
- 使用部分索引:只对常用文档创建索引
索引性能优化
- 调整索引键顺序:将选择性高的字段放在前面
- 使用覆盖索引:减少磁盘 I/O
- 避免过度索引:每个集合的索引数量建议不超过 64 个
- 使用 TTL 索引:自动管理过期数据
- 考虑索引压缩:减少索引存储空间
索引创建最佳实践
- 在低峰期创建:避免影响生产环境
- 使用后台创建:对于大型集合,使用 background: true
- 监控创建进度:使用 db.currentOp() 查看索引创建状态
- 测试查询性能:创建前后测试查询响应时间
- 记录索引变更:记录索引的创建、修改和删除操作
索引相关命令
索引信息查询
javascript
// 查看所有索引
db.collection.getIndexes()
// 查看索引大小
db.collection.totalIndexSize()
// 查看集合索引统计
db.collection.stats().indexDetails
// 查看索引使用情况
db.collection.aggregate([{ $indexStats: {} }])索引管理命令
javascript
// 创建索引
db.collection.createIndex({ field: 1 }, { background: true })
// 删除索引
db.collection.dropIndex("index_name")
// 删除所有索引
db.collection.dropIndexes()
// 重建索引
db.collection.reIndex()
// 压缩集合(包括索引)
db.runCommand({ compact: "collection_name" })索引分析命令
javascript
// 分析查询计划
db.collection.find({ field: "value" }).explain("executionStats")
// 查看查询性能统计
db.collection.find({ field: "value" }).explain("allPlansExecution")
// 分析索引使用情况
db.collection.aggregate([
{ $indexStats: {} },
{ $sort: { accesses.ops: -1 } }
])索引监控工具配置
配置查询分析器
yaml
# mongod.conf
operationProfiling:
mode: slowOp
slowOpThresholdMs: 100
rateLimit: 100启用索引统计
javascript
// 启用索引使用统计
db.setProfilingLevel(1, { slowms: 100 })
// 查看索引统计
db.serverStatus().indexCounters使用 Prometheus + Grafana 监控
- 安装 MongoDB 导出器
bash
docker run -d -p 9216:9216 -p 27017:27017 --name mongodb-exporter \
percona/mongodb_exporter:0.20 --mongodb.uri mongodb://localhost:27017- 配置 Prometheus
yaml
scrape_configs:
- job_name: 'mongodb'
static_configs:
- targets: ['mongodb-exporter:9216']- 创建 Grafana 仪表盘
- 导入 MongoDB 仪表盘模板(ID: 7353)
- 监控索引大小、索引使用次数、索引命中率等指标
常见索引问题及解决方案
1. 索引过大
问题:索引占用过多存储空间
解决方案:
- 删除未使用的索引
- 优化复合索引,合并类似索引
- 使用部分索引减少索引大小
- 考虑使用索引压缩(企业版)
2. 索引更新缓慢
问题:写操作因索引更新而延迟
解决方案:
- 减少索引数量
- 优化索引结构
- 考虑使用更高效的索引类型
- 增加硬件资源(CPU、内存、磁盘)
3. 索引碎片过多
问题:索引碎片导致查询性能下降
解决方案:
- 重建索引
- 使用 compact 命令压缩集合
- 优化写操作模式,减少碎片化
4. 索引不被使用
问题:创建的索引没有被查询使用
解决方案:
- 检查查询模式是否与索引匹配
- 调整索引键顺序
- 考虑使用不同的索引类型
- 删除未使用的索引
5. 索引构建时间过长
问题:创建索引需要很长时间
解决方案:
- 在低峰期创建索引
- 使用 background: true 选项
- 考虑使用滚动索引构建
- 增加硬件资源
索引维护计划
每日维护任务
- 监控索引使用情况:查看慢查询日志,识别索引问题
- 检查索引构建状态:确保没有长时间运行的索引构建任务
- 监控索引大小变化:发现异常增长的索引
每周维护任务
- 分析索引使用统计:识别未使用和低效的索引
- 检查索引碎片:评估是否需要重建索引
- 优化查询计划:根据索引使用情况调整查询
每月维护任务
- 重建碎片化严重的索引:提高索引性能
- 删除未使用的索引:释放存储空间
- 审查索引策略:根据业务变化调整索引设计
季度维护任务
- 全面索引审查:评估所有索引的有效性
- 更新索引设计:根据查询模式变化调整索引
- 测试索引性能:对比优化前后的查询性能
常见问题(FAQ)
Q1: 如何判断是否需要重建索引?
A1: 当出现以下情况时,应该考虑重建索引:
- 索引碎片率超过 30%
- 查询性能明显下降
- 索引大小异常增长
- 频繁出现索引相关的慢查询
- 长时间运行的索引构建任务
Q2: 重建索引会影响生产环境吗?
A2: 重建索引会占用系统资源,可能影响生产环境性能。建议:
- 在低峰期进行索引重建
- 使用 background: true 选项(对于大型集合)
- 逐步重建索引,不要同时重建所有索引
- 监控系统资源使用情况
Q3: 如何监控索引的使用情况?
A3: 可以通过以下方式监控索引使用情况:
- 启用查询分析器,查看慢查询日志
- 使用 $indexStats 聚合管道查看索引使用统计
- 使用 db.serverStatus().indexCounters 查看索引计数器
- 使用第三方监控工具(Prometheus + Grafana、Datadog 等)
Q4: 未使用的索引会影响性能吗?
A4: 是的,未使用的索引会:
- 占用存储空间
- 增加写操作的开销(每次写操作都需要更新所有索引)
- 增加内存使用(索引需要加载到内存中)
- 影响查询优化器的决策
Q5: 如何优化复合索引的顺序?
A5: 复合索引的顺序应该根据查询模式确定:
- 首先放置选择性高的字段
- 其次放置常用于精确匹配的字段
- 最后放置用于范围查询或排序的字段
- 考虑查询条件的组合方式
Q6: 索引数量过多会有什么影响?
A6: 索引数量过多会:
- 增加存储空间占用
- 增加写操作的延迟
- 降低写操作的吞吐量
- 影响查询优化器的性能
- 增加管理复杂度
Q7: 如何使用部分索引减少索引大小?
A7: 部分索引只包含满足特定条件的文档,可以通过 partialFilterExpression 选项创建:
javascript
db.collection.createIndex(
{ field: 1 },
{ partialFilterExpression: { status: { $eq: "active" } } }
)Q8: 如何监控索引构建进度?
A8: 可以使用以下方法监控索引构建进度:
javascript
// 查看索引构建状态
db.currentOp({
$or: [
{ "msg": /Index build/ },
{ "cmd.createIndexes": { $exists: true } }
]
})
// 查看索引构建进度(MongoDB 4.2+)
db.collection.aggregate([
{ $listIndexes: {} }
])Q9: 如何处理索引构建失败?
A9: 索引构建失败的处理方法:
- 查看错误日志,确定失败原因
- 检查硬件资源是否充足
- 尝试在低峰期重新构建
- 考虑使用更小的批量大小
- 对于大型集合,考虑使用滚动索引构建
Q10: 如何备份和恢复索引?
A10: 索引通常不需要单独备份,因为:
- 索引可以从数据中重新构建
- 备份数据后,可以在恢复时重建索引
- 对于大型集合,可以考虑备份索引定义,以便快速重建
javascript
// 备份索引定义
const indexes = db.collection.getIndexes()
printjson(indexes)
// 从定义中重建索引
indexes.forEach(idx => {
if (idx.name !== "_id_") {
delete idx.v
delete idx.ns
delete idx.name
db.collection.createIndex(idx.key, idx)
}
})