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()。
COLLSCAN 或扫描大量文档。$ne/$nin、$where、字段类型不一致。allowDiskUse。$lookup 的被关联字段无索引,或 $in 数组过大、multikey 索引扇出严重。limit/projection,返回大文档或超大结果集。executionStats 验证 docsExamined ≈ nReturned。_id 游标,避免大 skip。maxTimeMS,防止个别长查询拖垮实例。$lookup 的被关联字段建索引;能预聚合的指标改用预计算集合。allowDiskUse。(RAM - 1GB) × 50% 配置(不低于 256MB),监控 dirty 比例和应用线程淘汰。executionStats 中哪两个字段最能说明索引是否有效?durationMillis 为什么可能误导定位?totalDocsExamined 与 nReturned;两者接近且计划为 IXSCAN 才说明索引有效。winningPlan 和 rejectedPlans。executionStats 的执行时间和服务端指标判断真正瓶颈。