冗余索引指被另一索引完全覆盖的索引,如{a:1}被{a:1,b:-1}覆盖;删前须先隐藏验证,再逐节点drop,避免副本集不同步、I/O阻塞及硬编码依赖问题。

怎么判断一个索引是不是冗余的
冗余索引不是靠“看起来像”来判断的,而是看它是否被另一个索引完全覆盖。比如集合里同时存在 { a: 1 } 和 { a: 1, b: -1 },前者就是冗余的——所有能用 { a: 1 } 的查询,{ a: 1, b: -1 } 都能走,还多带一个字段排序能力。
注意这些常见误判点:
-
{ b: -1 }即使和{ a: 1, b: -1 }共享字段,也不冗余——它支持只查b的场景 - 方向不一致就算不覆盖:
{ a: 1 }不能被{ a: -1 }覆盖,升序索引无法服务降序查询 - 稀疏索引、部分索引、带 collation 的索引,即使 key 相同,也不能简单视为冗余——语义不同
删之前必须先隐藏,而不是直接 drop
MongoDB 4.4+ 支持 collMod 隐藏索引,这是生产环境删索引前不可跳过的步骤。隐藏后索引不参与查询计划,但依然消耗写入开销和磁盘空间,相当于“暂停使用”而非“物理删除”。
执行方式很简单:
- 隐藏:
db.runCommand({ collMod: "collection", index: { name: "old_idx" }, hidden: true }) - 验证:用
explain("executionStats")查关键查询,确认执行计划里不再出现该索引名 - 观察至少一个完整业务周期(比如 24 小时或一个流量高峰),监控慢查询率、CPU 和写延迟是否明显上升
如果出问题,取消隐藏只要一行命令:db.runCommand({ collMod: "collection", index: { name: "old_idx" }, hidden: false }),比重建快得多。
dropIndex 在副本集上不会自动同步
这是线上事故最高发的坑:你在主节点执行了 db.collection.dropIndex("idx"),返回 ok: 1,就以为完事了。其实从节点索引还在,后续某些查询可能因从节点选错执行计划变慢,甚至因索引残留导致 unique 冲突报错。
正确做法是手动逐节点操作:
- 先用
rs.status()拿到所有stateStr: "PRIMARY"或"SECONDARY"的节点地址 - 挨个连上去执行
db.collection.dropIndex("idx"),别依赖主节点 oplog 同步——dropIndex不记 oplog - 特别注意延迟节点和隐藏节点:它们可能长期离线,上线后不会自动清理已删索引,得单独连上去处理
大索引删除时的 I/O 和锁风险
删一个几 GB 的复合索引,不是秒级操作。MongoDB 会逐页释放 B-tree 结构,触发大量随机磁盘写,可能拖慢其他请求。
规避方法很实际:
- 避开高峰期,选在凌晨低峰执行
- 加
maxTimeMS防卡死:db.runCommand({ dropIndexes: "collection", index: "big_idx", maxTimeMS: 60000 }) - 删完立刻检查:
db.currentOp({ "secs_running": { "$gt": 5 } })看有没有残留长事务,db.collection.getIndexes()确认消失 - 旧版本(4.0 及以前)删唯一索引或 TTL 索引仍会短暂全局锁写,务必提前通知上下游服务
真正麻烦的从来不是“怎么删”,而是“删完谁还在用它”。索引没被 explain 捕获,不等于没被应用代码硬编码依赖——上线前翻一遍 DAO 层的 hint 调用,比跑十遍脚本都管用。


















