慢查询日志关键看keysExamined和docsExamined是否远大于nreturned,结合planSummary判断索引使用效率;复合索引须严格按ESR原则(等值→排序→范围)设计;覆盖查询要求所有查询及投影字段均在索引中;避免盲目建索引,警惕multikey索引膨胀和低效旧索引。

看懂慢查询日志里的关键字段
慢查询日志不是只看“花了多少毫秒”,重点是 keysExamined 和 docsExamined 这两个数字。如果它们远大于 nreturned,说明索引在“扫了很多但没用上多少”。比如日志里出现 keysExamined:1498 docsExamined:1498 nreturned:3,就是典型的低效索引——99%的扫描动作白做了。
还要注意 planSummary 字段:IXSCAN 表示用了索引,COLLSCAN 表示全表扫描;IXSCAN 后面跟着的字段顺序,就是当前索引的实际结构,比如 IXSCAN {alxxxId:1.0, itemTagList:1.0} 就是复合索引 {alxxxId: 1, itemTagList: 1}。
按 ESR 原则重建复合索引
MongoDB 查询优化器对复合索引字段顺序极其敏感。ESR(Equality → Sort → Range)不是建议,是执行路径的硬约束:
- 等值条件(
$eq、_id、字符串精确匹配)必须放最左 - 排序字段(
.sort({field: 1}))紧随其后 - 范围条件(
$gt、$in、$regex)必须放最后
原日志中查询含 {alxxxId:"xxx", state:0, itemTagList:{$in:[...]}, persxxal:0},但索引只有 {alxxxId:1, itemTagList:1},state 和 persxxal 被跳过,导致 itemTagList 变成范围扫描起点。正确索引应为:db.collection.createIndex({alxxxId: 1, state: 1, persxxal: 1, itemTagList: 1})。
验证索引是否真正覆盖查询
覆盖查询(Covered Query)意味着所有查询字段和返回字段都在索引里,完全不用读文档。它能直接把 docsExamined 降到 0。
检查方式:运行 db.collection.find(query).explain("executionStats"),看返回结果中:
-
executionStats.totalDocsExamined === 0且 executionStats.totalKeysExamined > 0
若不满足,说明需要把投影字段也加进索引。例如查询 find({a:1}, {b:1, c:1}),索引至少得是 {a:1, b:1, c:1};若还带 .sort({b:1}),就按 ESR 把 b 提前。
避免盲目建索引的三个坑
索引不是越多越好,每个索引都吃内存、拖慢写入、增加磁盘压力:
-
text索引或含数组字段的multikey索引,体积可能超原始数据 50%,上线前必须用db.collection.stats()查indexSize - 复合索引中只要有一个字段是数组,整个索引就变成
multikey,即使其他字段是标量 - 不要为单个
$in查询单独建索引——$in在等值位置时可走索引,但若出现在中间或末尾,大概率失效
真正该删的,往往是那些 keysExamined / nreturned > 100 且长期没被 winningPlan 选中的旧索引。用 db.collection.getIndexes() 配合日志分析,比凭感觉更可靠。











