高并发下索引更新本身不直接竞争,真正瓶颈是WiredTiger文档锁和内存页争用;建索引卡住写操作源于DDL锁粒度问题,而非索引更新竞争。

高并发下索引更新本身不直接竞争——真正争用的是写入路径上的 WiredTiger 文档锁和内存页争用。索引是写操作的副产品,瓶颈从来不在“更新索引”这个动作,而在“高频写同一文档/同一分片/无索引定位”引发的底层资源排队。
为什么createIndex或dropIndex期间会卡住写操作
后台建索引({background: true})仍会持有 collection 级读锁,阻塞update、delete等写操作;前台建索引则全程加写锁,整个集合不可写。这不是索引“更新竞争”,而是 MongoDB 的 DDL 操作锁粒度问题。
- 建索引时若集合正被大量
findOneAndUpdate高频修改,db.currentOp()中会出现大量操作卡在acquiringLock状态,且集中在 primary 节点 -
mongostat里locked db列持续非零,或conn数突增但netIn未同步上升,说明连接在排队等锁 - Robo 3T 的索引管理界面显示“building”状态超过预期时间,同时
db.collection.stats().indexCount不变,基本可判定被写操作阻塞
单文档高频更新导致的索引路径争用
即使有完美索引,对同一文档反复执行updateOne也会触发 WiredTiger 的文档级写锁排队。索引字段本身不是重点,关键是“定位+修改+刷脏页”整条链路在内存页层面产生争用。
- 现象:
db.serverStatus().metrics.operation.writeOps分布正常,但wt.cache.tracked_dirty_bytes飙升、page faults/sec > 10,说明缓存压力大,索引查找快但写入慢 - 解决方向不是“优化索引”,而是降低单文档更新频率:用
$inc替代$set减少日志体积;合并多次小更新为一次批量updateMany(条件必须能命中索引) - 避免在索引字段上做函数操作,例如
{ createdAt: { $gt: { $dateFromString: ... } } }会让索引失效,退化为 COLLSCAN,放大锁等待
分片集群中因分片键不当引发的隐性索引争用
分片键选错不会让索引“变慢”,但会让所有写请求打到同一个分片,把该分片的索引 B-tree 节点、WiredTiger cache、journal 刷盘全部推到极限——其他分片的索引再好也毫无意义。
- 典型错误:
{ createdAt: 1 }或{ _id: "hashed" }单独作分片键,sh.status()显示某分片 chunk 数是其他分片的 5 倍以上 - 验证方法:
db.collection.stats({scale: 1024*1024})对比各分片的size和count,偏差超 3 倍即存在倾斜 - 紧急缓解:用
sh.splitAt()强制拆分热点 chunk,但治标不治本;长期方案必须重建集合,换用{ userId: 1, timestamp: 1 }类复合分片键,并确保userId在查询条件中高频出现
最常被忽略的一点:索引竞争问题往往被误判为“CPU 高”或“磁盘慢”,但真实瓶颈可能藏在wt.cache.heartbeat频繁触发导致的脏页刷写抖动里——此时看mongotop -o 5比看 CPU 使用率更准。

















