索引过多会严重消耗wiredtiger缓存并拖慢读写性能:预热b-tree导致内存占用高(cache使用率>85%需警惕),深度≥6或internalpages占比>40%的索引低效;写入时多索引同步更新加剧锁等待与页分裂;查询因b-tree过深引发随机访问瓶颈,低命中率(

索引太多会直接吃光 wiredtiger 缓存
MongoDB 的 WiredTiger 存储引擎把索引页加载进内存缓存(cache)里加速查找,但不是“按需加载”——它倾向于预热整棵 B-Tree,尤其是内部节点页。树越深、页越多,驻留内存的索引页就越多。serverStatus().cache 里的 bytes currently in the cache 和 maximum bytes configured 对比,如果长期 >85%,基本就是索引在抢内存。
- 用
db.collection.stats({indexDetails: true})查每个索引的tree.depth,≥6 就要警惕 -
indexDetails.<name>.tree.internalPages</name>占比过高(比如 >40%),说明这棵树“胖”且低效,不是数据多,是结构稀疏 - 别只看
totalSize:磁盘上占 100MB 的索引,缓存里可能常驻 300MB+ 的页(因页分裂、填充率低)
写入变慢不光因为多更新几份数据
每次 insert/update/delete,MongoDB 不仅要改文档,还要同步更新所有匹配字段的索引条目。索引越多,写操作的原子性开销越大,尤其在高并发插入场景下,wiredTiger.lock 等待时间明显上升。
- 复合索引
{a: 1, b: 1}和单字段索引{a: 1}同时存在时,a字段的变更要刷两次索引页 - 带排序方向的索引(如
{createdAt: -1})更容易触发页分裂,加剧写放大 - 批量插入前用
db.collection.dropIndexes()临时删索引,导入完再重建,比边插边维护快 3–5 倍(实测中等规模集合)
查询反而更慢:B-Tree 深度超标后,随机内存访问成瓶颈
索引不是越多越好,关键是“命中率”。当 indexDetails.<name>.accesses.ops</name> / 总查询数
- 用
db.collection.find(...).explain("executionStats")看executionStages.stage是IXSCAN还是COLLSCAN,再核对keysExamined和nReturned比值 —— 如果是 500:1,说明索引区分度极差 - 升序索引
{status: 1, _id: 1}通常比{status: 1, createdAt: -1}更稳:因为_id天然单调递增、分布均匀,大幅减少页分裂 - 避免为
$in或低基数字段(如status只有 3 个值)单独建索引,除非配合高区分度字段做复合前缀
真正压垮集群的,往往是“看不见”的索引碎片
页分裂不是一次性事件,而是持续过程。一个频繁更新的索引,内部页碎片率可能高达 60% 以上,导致同样数量的数据,需要更多页加载进内存,resident 内存飙升,但 mapped 却没变——这是典型的“缓存污染”。
查 db.collection.stats({indexDetails: true}) 里的 indexDetails.<name>.tree.fragmentation</name>,>30% 就该考虑重建。用 db.collection.reIndex() 比删了再建更安全,但它会阻塞写入;生产环境建议在低峰期执行,或先在副本集 secondary 上验证效果。











