← 中文资源

简体中文版本

MongoDB 慢查询 — 用 Profiler 与 explain() 精准定位

开启数据库 Profiler 并按耗时排序,结合 executionStats 判断 COLLSCAN、索引选择性与内存排序,修复 MongoDB 慢查询。

阅读英文原文

现象

接口延迟升高但 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 均下降。