根本原因在于早期版本(如3.6及之前)默认前台创建索引,需持有全局或库级排他锁,阻塞所有读写操作;4.2起优化为仅在开始和结束阶段持短暂集合级排他锁,大幅提升并发性。

MongoDB索引创建会锁库,根本原因在于早期版本(background参数有没有写对。
3.x 及更早版本:后台索引 ≠ 不锁,只是锁得“轻一点”
在 MongoDB 3.6 及之前,background:1 确实能避免库级写锁阻塞读写,但代价是:
- 索引构建全程持有 collection 级
IX(意向排他锁),不影响其他集合,但本集合的写操作仍可能被短暂阻塞(尤其 WiredTiger 引擎下,实际是文档级锁,但建索引时仍需扫描全量文档) -
background模式本质是“让出锁再抢回”,每处理一批文档就释放锁,所以 QPS 波动明显,且总耗时比前台长 20%–50% - 若误写成
db.col.ensureIndex({a:1}, {background:true}, {name:"idx_a"})—— 多了一个括号,background就被忽略,退化为前台建索,直接拿库级写锁
4.2+ 版本:锁只在头尾“闪一下”,基本可视为无感
从 4.2 开始,索引构建改用“两阶段原子切换”机制,background 参数已废弃(默认就是并发行为):
- 仅在开始(注册新索引元数据)和结束(原子替换旧索引指针)两个瞬间,对集合加短暂排他锁(
X),通常 - 中间阶段使用类似 background 的方式扫描文档、写 side-write table,全程只持
IS(意向共享锁)或IX,读写照常 - 即使集合正在高频
update或insert,只要不是极端写入压力(如每秒数万写),业务请求基本无感知
WiredTiger 引擎下,锁行为还受写入模式影响
启用 WiredTiger(3.2+ 默认)后,锁粒度降到文档级,但索引创建本身仍是集合维度的操作:
- 建索引期间,WiredTiger 仍需遍历所有文档,每个文档的读取会触发其自身的 MVCC 版本检查,不阻塞其他线程对该文档的写,但会对该文档所在 page 加 latch(轻量内存锁)
- 若集合存在大量短事务 + 高频更新同一文档(如计数器场景),建索引可能加剧 latch 竞争,表现为 CPU 上升、个别写请求延迟升高,但不会出现“整个库卡住”
- 删除索引(
dropIndex)在 4.2+ 是立即生效的元数据操作,不锁集合;但重建(reIndex)仍是前台操作,会短暂锁集合,应避免在高峰期执行
别信“background 就安全”,先看版本和语法
很多线上事故源于对版本和语法的混淆:
- 3.6 集群里写了
{background: true},结果因 driver 版本低或 shell 解析 bug,参数未生效 → 实际前台建索 → 库锁 - 4.4+ 集群里仍显式传
background,虽然不报错,但会被忽略,容易让人误以为“加了 background 就保险”,放松对构建窗口的评估 - 真正需要关注的不是“有没有锁”,而是“锁在哪、持续多久、是否重叠业务高峰”。建议在执行前用
db.currentOp({secs_running: {$gt: 1}})看当前长耗时操作,避开已有压力时段
最易被忽略的一点:即使 4.4+ 版本建索引不锁库,它仍会显著拉升磁盘 I/O 和 CPU 使用率,尤其当集合 >10GB 或文档含大字段(如 base64 图片)时,可能触发系统 OOM Killer 或拖慢副本集同步——这比锁更难监控,也更常导致雪崩。











