Profiler不直接标注“缺索引”,需结合system.profile中planSummary为COLLSCAN、docsExamined远大于nReturned(如10万vs10)来判定;启用db.setProfilingLevel(1,{slowms:100})后,用db.system.profile.find({“planSummary”:“COLLSCAN”,“docsExamined”:{"$gt":1000},“nReturned”:{"$lt":100}})定位,并通过explain("executionStats")验证索引是否命中。

如何确认 profiler 已捕获到索引缺失的慢请求
Profiler 本身不直接标注“缺索引”,它只记录实际执行耗时和扫描行为。真正暴露索引问题的是 system.profile 中的几个关键字段组合:当 planSummary 为 COLLSCAN,且 docsExamined 远大于 nReturned(比如 10 万 vs 10),基本可断定没走索引。
注意:planSummary 字段在 db.currentOp() 和 system.profile 中都存在,但只有 profiler 记录的操作才带完整执行统计(如 keysExamined、docsExamined),currentOp 里这些值通常为空或不准确。
- 必须先启用 profiler:执行
db.setProfilingLevel(1, { slowms: 100 }),再验证db.getProfilingStatus()返回"was": 1 - 不要依赖
command.name === "find"判断是否慢——要查millis >= slowms且planSummary === "COLLSCAN" - 聚合类操作(如
aggregate)同样会显示COLLSCAN或IXSCAN,但需配合explain("executionStats")复现才能看到各阶段的docsExamined
从 system.profile 中快速定位全表扫描请求
直接查 system.profile 时,别只盯着 millis 排序。重点用这个查询筛出高扫描低返回的嫌疑请求:
db.system.profile.find({
"planSummary": "COLLSCAN",
"docsExamined": { "$gt": 1000 },
"nReturned": { "$lt": 100 }
}).sort({ "ts": -1 }).limit(10)
返回结果中每条记录对应一次真实执行的慢操作,ns 字段告诉你发生在哪个集合,command.filter 是查询条件,command.sort 是排序字段——这些就是建索引的直接依据。
-
docsExamined高 +nReturned低 = 条件筛选效率差,优先检查filter中字段是否建了索引 -
docsExamined高 +nReturned高 = 数据量大但没过滤,可能是缺少前置$match,或索引覆盖不全 - 如果
command里有sort但无对应索引,即使planSummary是IXSCAN,也可能因内存排序变慢——这时keysExamined会远小于docsExamined
用 explain 验证索引是否真被命中
Profiler 捕获的是“过去发生了什么”,而 explain("executionStats") 是“现在会怎么执行”。对 profiler 中发现的慢请求,必须拿原始 filter 和 sort 复现并解释:
db.users.find({ "status": "active", "createdAt": { "$gt": ISODate("2025-01-01") } })
.explain("executionStats")
看输出里的 executionStages.stage 是否为 IXSCAN,以及 executionStats.totalDocsExamined 是否接近 nReturned。如果仍是 COLLSCAN,说明索引没建对或没生效。
- 复合索引顺序很重要:
db.users.createIndex({"status": 1, "createdAt": 1})能加速上面查询;反过来建则无效 - 正则查询(
$regex)只有前缀匹配(如^abc)才能走索引,.*abc必然COLLSCAN - 使用
$in时,若数组过大(> 数百项),MongoDB 可能放弃索引选择全扫,此时 profiler 也会显示COLLSCAN
建索引时容易忽略的生产约束
在 profiler 确认缺索引后,建索引不是简单跑一条 createIndex 就完事。线上集合数据量大时,同步建索引会阻塞写入,还可能触发内存溢出。
必须加 { background: true },否则整个集合会被锁死:
db.users.createIndex({ "email": 1 }, { background: true })
-
background: true是底线要求,哪怕集合只有几万文档——它让索引在后台分批构建,不影响正常读写 - 避免在高峰期建大索引;可用
db.currentOp({ "secs_running": { "$gt": 60 } })监控长事务,避开业务高峰 - 建完索引后,别忘了再跑一次 profiler 查询,确认
planSummary已变成IXSCAN,且docsExamined显著下降
真正卡住人的往往不是“要不要建索引”,而是建完之后 profiler 里还出现同一条 COLLSCAN ——那大概率是索引字段类型不一致(比如存的是字符串 "123",查询却用了数字 123),或者应用代码里拼错了字段名。这种细节,profiler 不会提醒你,只能靠比对 command.filter 和实际索引定义。


















