单次查询是否走索引,须看explain("executionstats")中nreturned与totaldocsexamined比值及stage:接近则collscan未走索引,远小于则ixscan但效率低,相等且值小为理想;长期是否被用则查$indexstats的accesses.ops是否持续为0。

索引“存在”不等于“有效”——真正要查的是它有没有被查询实际用上、用得是否高效。
怎么看单次查询到底走没走索引
核心就看 explain("executionStats") 返回里两个数字的比值:executionStats.nReturned 和 executionStats.totalDocsExamined。
- 如果两者接近(比如
nReturned是 98,totalDocsExamined是 102),基本就是COLLSCAN,索引完全没参与 - 如果
nReturned很小但totalDocsExamined很大(比如返回 5 条却扫了 8 万条),说明走了索引但只用了前缀,后段字段没命中 - 理想情况是两者相等,且
totalDocsExamined数值本身很小(比如都是 12),同时executionStats.executionStages.stage显示为IXSCAN
别只看 queryPlanner 阶段——它只是 MongoDB “打算”怎么查;executionStats 才是真实跑完后交的答卷。
怎么知道这个索引在生产环境里是不是“幽灵索引”
单次 explain 看不出长期使用情况。要用 $indexStats 聚合阶段查真实访问次数:
- 执行
db.collection.aggregate([ { $indexStats: {} } ]) - 重点盯
accesses.ops:这个数是自 mongod 启动以来该索引被真正用于查找/排序的次数 - 如果连续几小时
accesses.ops没变过,或者一直是 0,那它大概率是“挂名索引” - 注意
accesses.since时间戳——如果 mongod 刚重启过,ops为 0 就不能立刻下结论
尤其警惕那些 ops > 0 但全是 count()、distinct() 或监控脚本触发的索引,它们对业务查询没实质贡献。
聚合管道里索引为什么“明明写了$match却没生效”
聚合中索引只对原始集合生效,且只在特定阶段起作用:
-
$match和$sort必须放在最前面,且字段必须包含在索引中;一旦中间插了$project、$addFields或$unwind,后续$match就只能扫内存里的中间结果,和原集合索引无关 -
$lookup的被联查集合,过滤条件必须写在pipeline内部(不是外层),且对应字段得有独立索引 - 执行
db.collection.explain("executionStats").aggregate([...]),确认第一个$match对应的stage是IXSCAN,而不是COLLSCAN或SORT
很多人在 $unwind 后加 $match,explain 显示 IXSCAN,误以为索引生效——其实扫的是展开后的文档流,不是原始集合。
模糊查询、$or、函数操作这些常见坑怎么快速识别
这些场景容易让索引“表面可用,实际失效”:
-
$regex模式以/abc/开头(没加^),B-tree 索引无法下推,退化为全表扫描 -
$or各分支字段不一致,比如{ $or: [ { a: 1 }, { b: 2 } ] },MongoDB 通常弃用索引改COLLSCAN - 对索引字段做了函数操作,如
{ ts: { $gt: { $add: ["$ts", 3600] } } },索引无法直接比较 - 复合索引顺序错位:索引是
{ a: 1, b: 1, c: 1 },但查询只用了b和c,explain里indexBounds中a的范围会是[MinKey, MaxKey],说明第一字段未约束
真正难判断的,不是索引存不存在,而是它在哪些查询路径里悄悄失效了——尤其是聚合管道嵌套、跨集合关联、带时区转换或动态字段计算的场景,executionStats 和 $indexStats 必须交叉验证才靠得住。











