现象
接口延迟升高但 CPU、内存看起来正常;Profiler 中出现数百毫秒的查询;explain() 显示本应走索引的查询却是 COLLSCAN;docsExamined 远大于 nReturned。
诊断
开启慢操作记录并读取最慢的几条:
db.setProfilingLevel(2, { slowms: 100 })
db.system.profile.find({ millis: { $gt: 100 } })
.sort({ millis: -1 }).limit(5)
.forEach(d => printjson({
ns: d.ns, millis: d.millis, planSummary: d.planSummary,
docsExamined: d.docsExamined, nreturned: d.nreturned
}));
planSummary 为 COLLSCAN 表示全表扫描,IXSCAN 表示用了索引;docsExamined 远高于 nreturned 说明索引对当前谓词选择性不足。再用 explain("executionStats") 查看执行计划:
db.orders.find({ customerId: "C-1042", status: "open" })
.sort({ createdAt: -1 }).limit(20)
.explain("executionStats")
重点看 totalKeysExamined、totalDocsExamined、nReturned,以及 winningPlan 中是否出现阻塞式 SORT。用 db.currentOp({ secs_running: { $gt: 5 }, active: true }) 排除其他长操作占满 I/O 或持锁。
修复
按“等值字段 → 排序字段 → 范围字段”的顺序建立复合索引,使查询既能命中过滤条件又能直接按序返回:
db.orders.createIndex(
{ customerId: 1, status: 1, createdAt: -1 },
{ name: "orders_by_customer_status_recent" }
)
同时加上投影和 limit,减少回表与网络传输;删除前缀重复的冗余索引。大集合建索引应安排在低峰期,并观察复制延迟。
验证
重新执行 explain("executionStats"),确认走 IXSCAN、没有阻塞 SORT,且 totalDocsExamined 接近 nReturned。保持 Profiler 运行一个业务周期,确认超过 slowms 的记录消失,同时 p95 延迟与 CPU 均下降。