mongodb-slow-query

MongoDB 查询延迟升高、应用超时,慢查询日志显示扫描量远大于返回量。

现象

接口 p99 上升,应用报 Mongo 超时;mongod CPU/IO 偏高,db.currentOp() 里能看到长时间运行的查询。日志中的 slow query 往往伴随 docsExamined 远大于 nreturned。

快速定位

先看当前有没有阻塞或长操作,再看历史慢查询与执行计划:

mongosh "mongodb://<user>:<pass>@<host>:27017/admin" --eval 'db.currentOp({active: true, secs_running: {$gt: 1}})'
grep -E 'Slow query|durationMillis' /var/log/mongodb/mongod.log | tail -20

开启数据库级 profiler,并读取最近的慢查询:

mongosh "mongodb://<user>:<pass>@<host>:27017/admin" --eval 'db.getSiblingDB("app").setProfilingLevel(1, {slowms: 100})'
mongosh "mongodb://<user>:<pass>@<host>:27017/admin" --eval 'db.getSiblingDB("app").system.profile.find({millis: {$gt: 100}}).sort({ts: -1}).limit(5).pretty()'

对具体查询跑 executionStats,重点看 totalKeysExamined、totalDocsExamined、nReturned 和 winningPlan.stage:

mongosh "mongodb://<user>:<pass>@<host>:27017/app" --eval 'db.orders.find({userId: "u1", status: "paid"}).sort({createdAt: -1}).limit(20).explain("executionStats")'
mongosh "mongodb://<user>:<pass>@<host>:27017/app" --eval 'db.orders.getIndexes()'

最后看 WiredTiger 缓存压力和操作计数:

mongosh "mongodb://<user>:<pass>@<host>:27017/admin" --eval 'const c = db.serverStatus().wiredTiger.cache; printjson({used: c["bytes currently in the cache"], max: c["maximum bytes configured"], dirty: c["tracked dirty bytes in the cache"], appEvictions: c["pages evicted by application threads"]})'

易误判:日志里的 durationMillis 包含排队、等锁和网络时间,不全是执行时间;docsExamined 高但耗时短也未必是问题,关键看它相对 nReturned 的比例和 p99。另一个坑是加了索引却未被选中 —— 必须用 executionStats 确认走了 IXSCAN,而不是只看 getIndexes()。

常见根因

  1. 缺索引或复合索引顺序不对,查询走 COLLSCAN 或扫描大量文档。
  2. 查询写法让索引失效:非锚定正则、$ne/$nin、$where、字段类型不一致。
  3. 排序无索引导致内存排序,超过 32MB 后报错或被迫 allowDiskUse。
  4. $lookup 的被关联字段无索引,或 $in 数组过大、multikey 索引扇出严重。
  5. 无 limit/projection,返回大文档或超大结果集。
  6. 写热点、长事务或锁竞争,导致查询大量时间在等待。
  7. WiredTiger 缓存不足,频繁淘汰,应用线程参与 eviction。
  8. 从节点读遇到复制延迟或 read concern 等待,表现为查询慢。

修复

相关命令

读完自测

  1. executionStats 中哪两个字段最能说明索引是否有效?
  2. 为什么加了索引查询仍然可能慢?
  3. 慢查询日志里的 durationMillis 为什么可能误导定位?
答案
  1. totalDocsExamined 与 nReturned;两者接近且计划为 IXSCAN 才说明索引有效。
  2. 索引顺序不符合 ESR、选择性低、查询写法无法利用索引,或优化器选了其他计划;需看 winningPlan 和 rejectedPlans。
  3. 它包含排队、等锁和网络时间;要结合 executionStats 的执行时间和服务端指标判断真正瓶颈。