mongodb高并发建索引易致写入阻塞,主因是ddl锁粒度与wiredtiger资源争用;须确认引擎为wiredtiger、避开高峰、设maxtimems超时、>50gb需ssd、失败用indexbuildretry续建、监控锁等待及分片均衡。

在MongoDB高并发生产库中,建索引操作极易引发写入阻塞、事务排队甚至接口超时,根本原因不是索引本身慢,而是DDL锁粒度与WiredTiger底层资源争用叠加所致。
确认当前建索引是否真在后台运行
执行 db.serverStatus().storageEngine.name,确保返回 wiredTiger;若为 mmapv1(已弃用),【必须立即升级引擎】,否则所有后台选项均无效。
运行 db.currentOp({ "secs_running": { "$gt": 30 } }),检查输出中是否存在 createIndex 操作且 waitingForLock: true —— 这说明它正被其他写操作卡住,而非“后台”运行。
注意:MongoDB 4.2+ 的 { background: true } 仅避免阻塞其他索引构建,【不豁免对 update/delete/findOneAndUpdate 的锁排队】。
避开业务高峰并设置安全兜底
第一步:在低峰期执行,例如凌晨 2:00–4:00,避开订单支付、日终结算等核心窗口。
第二步:强制加超时保护,命令格式为:db.collection.createIndex({ status: 1, createdAt: -1 }, { background: true, maxTimeMS: 7200000 })(2小时)。
第三步:建索引前先用 db.collection.stats({ scale: 1024*1024 }) 查看集合大小,若 >50GB,需额外评估磁盘 I/O 能力;SSD 是硬性前提,HDD 上建大索引大概率失败。
用 collMod + indexBuildRetry 替代删索引重来
方法一:建索引中途失败(如节点重启、磁盘满),不要执行 db.collection.dropIndex(...)。
直接运行:db.runCommand({ collMod: "mycol", indexBuildRetry: true }),MongoDB 会自动续建未完成的索引。
方法二:若已有同名索引残留(状态为 building 但卡死),先查其名称:db.collection.getIndexes(),再用 db.runCommand({ killOp: 1, op: <opid> })</opid> 终止对应 opid,最后再触发 retry。
这一步操作起来很简单,直接把命令复制进 shell 就行。但切记:retry 不会重建已损坏的索引页,只继续未完成阶段。
监控锁等待真实源头
执行 db.currentOp({ "waitingForLock": true, "secs_running": { "$gt": 5 } }),重点关注 ns(命名空间)和 secs_running 字段。
若发现大量操作卡在 acquiringLock 且都指向同一集合,同时 mongostat 中 locked db 列持续非零,说明建索引已被写流量反向阻塞——此时应暂停写入或限流,而非强行继续建索引。
用 db.serverStatus().metrics.locks.collection.acquireCount_ 对比 .acquireWaitCount_,若后者占前者比例 >5%,即存在明显锁竞争。
分片集群下必须验证分片键健康度
运行 sh.status(),检查各分片的 chunk 分布是否均匀;若某分片 chunk 数是其他分片的 3 倍以上,【此时建任何索引都无效】,所有写压力已集中于单一分片。
进一步验证:db.collection.stats({scale: 1024*1024}) 输出中对比各分片的 size 和 count,偏差超 3 倍即判定倾斜。
紧急处理只能临时拆分热点 chunk:sh.splitAt("db.col", { shardKeyField: ObjectId(...) }),但长期方案必须重建集合并更换复合分片键,例如 { userId: 1, timestamp: 1 }。











