totalKeysExamined远高于nReturned表明索引策略与查询条件不匹配,根本原因是索引无法有效过滤:低选择性字段前置、含$ne/$regex等不可用操作、隐式类型转换等均导致大量无效索引扫描。

当你在MongoDB中执行查询后发现totalKeysExamined数值远高于nReturned,说明数据库扫描了大量索引条目却只返回极少结果——这不是网络延迟或CPU瓶颈的问题,而是索引策略与查询条件严重不匹配的明确信号。
根本原因:索引无法有效过滤
totalKeysExamined统计的是MongoDB在选定索引上实际检查的索引键数量。它远大于nReturned,意味着查询引擎不得不遍历成千上万条索引记录,逐条比对是否满足全部查询条件。例如查{"status": "active", "created_at": {"$gte": ISODate("2025-01-01")}},若只在created_at上建了单字段索引,那所有该时间范围内的索引项都会被拉出来,再挨个回表读文档判断status是否为"active"——这一步就已造成大量无效索引扫描。
这一步操作起来很简单,直接把文件拖进去就行。
典型诱因与对应现象
方法一:使用了低选择性字段前置的复合索引
比如集合中有1000万条记录,status只有"pending"/"active"/"done"三个值,却建立索引{"status": 1, "user_id": 1, "created_at": -1}。查询db.orders.find({"status": "active", "created_at": {"$gt": ...}})时,前缀status几乎不缩小范围,导致totalKeysExamined ≈ 总索引项数 × 1/3,而nReturned可能只有几十。
方法二:查询条件中存在无法利用索引的操作
使用正则表达式开头带变量(如/^abc/可用索引,但/abc/或/.*abc/不可用)、$ne、$not、$exists(除非该字段有稀疏索引且查询能命中)、或者在索引字段上做函数运算(如{year: {$eq: {$year: "$date"}}})。这些都会让MongoDB跳过索引扫描,退化为全索引扫描甚至全表扫描。
注意:$exists: true在无稀疏索引时必然触发全索引扫描,totalKeysExamined会飙升到索引总长度,务必避免
验证与定位步骤
第一步:运行explain("executionStats")获取完整执行数据
db.collection.find({your_query}).explain("executionStats") → 检查返回体中的executionStats.totalKeysExamined、executionStats.nReturned、executionStats.totalDocsExamined三者关系。
第二步:确认winningPlan.stage是否为IXSCAN而非COLLSCAN
如果stage是COLLSCAN,说明根本没走索引;如果是IXSCAN但totalKeysExamined巨大,则问题出在索引设计或查询写法。
第三步:比对keyPattern与查询字段顺序
查看queryPlanner.winningPlan.keyPattern,确认查询中所有等值条件字段是否严格左对齐出现在索引最左侧,范围查询字段是否紧随其后且仅有一个。
第四步:检查是否存在隐式类型转换
例如索引字段是NumberLong,但查询传入的是NumberInt或字符串"123",MongoDB无法使用索引进行精确匹配,将被迫扫描更多键。此时explain中会出现"indexBounds"范围异常宽泛,甚至显示[MinKey, MaxKey]。


















