accesses.ops是MongoDB 5.0+中$indexStats返回的索引实际使用次数计数器,统计索引被真正用于查找/排序/范围扫描的次数,而非被选中或查询总次数。

直接看 $indexStats 返回的 accesses.ops 是否长期为 0 —— 如果连续数小时/天没增长,基本可判定该索引从未被查询命中,属于“幽灵索引”。
为什么不能只靠 explain() 判断索引是否“被使用”
explain("executionStats") 只反映单次查询行为,而真实线上环境里,一个索引可能在凌晨批处理时被用到,白天流量高峰却完全闲置。单次 explain 看不到这种时间维度的使用分布。$indexStats 是唯一能告诉你“这个索引在过去 N 小时内到底被查了多少次”的聚合阶段,它读取的是 MongoDB 内部维护的实时访问计数器(从 3.2 版本起持续累积)。
常见误判场景:
- 开发环境跑一次
explain显示用了IXSCAN,就以为索引“已生效”,但生产中该查询压根没被执行过 - 聚合管道里
$match放在$unwind后面,explain显示走了索引,但那是对中间结果集的扫描,不是对原始集合——$indexStats对这种索引完全无感,因为它只统计顶层集合的索引访问
$indexStats 的关键字段怎么看
执行 db.collection.aggregate([ { $indexStats: {} } ]) 后,每条输出代表一个索引,重点关注:
-
name:索引名(如"age_1_gender_1"),注意"_id_"是默认索引,别误删 -
accesses.ops:自实例启动以来该索引被命中的总次数;值为0或长期不增长,就是危险信号 -
accesses.since:计数器重置时间(通常是 mongod 重启时),如果这个时间很新,ops为 0 就不能立刻下结论 -
host和localTime:确认你查的是主节点(非从节点),且时间戳是当前实例的本地时间,避免跨节点比对偏差
如何识别“低效但有访问”的伪活跃索引
有些索引 accesses.ops > 0,但实际价值极低,比如:
- 只被
count()或distinct()这类元操作触发,而非业务主查询 - 被慢查询日志里出现频率极低的调试语句偶然触发(如
find({ _id: ObjectId(...) })走了_id索引,但业务里根本不用这种查法) - 被监控脚本定期调用的
stats()类命令带动,不代表业务逻辑依赖它
这时要交叉验证:db.setProfilingLevel(1, { slowms: 100 }) 开启慢查询日志,再查 system.profile 中哪些查询真正带了 indexName 字段,且 millis > 50。如果 profile 里完全找不到该索引名,那它的 ops 很可能只是噪音。
删除前必须确认的三件事
发现某个索引 accesses.ops === 0 并不等于可以立刻删:
- 检查是否有定时任务、ETL 脚本或离线分析作业在非高峰时段运行(比如凌晨 2 点的报表 job)
- 确认该索引是否被
$lookup的被联查集合所依赖——$indexStats在主集合上查不到,但它可能在另一张表里是关键索引 - 用
db.collection.getIndexes()看key字段,排除掉唯一索引("unique": true)或 TTL 索引("expireAfterSeconds"),这类索引即使不被查询,也有数据治理作用
最稳妥的做法:先 reIndex 或 collMod 把它设为隐藏索引(MongoDB 4.4+),观察一周,再决定是否物理删除。隐藏后它仍参与写入维护,但不会被查询优化器选中——这是线上环境唯一安全的“灰度下线”方式。











