根本原因是写路径分裂导致源分片与目标分片吞吐不匹配,叠加未适配writeconcern引发conflictingoperationinprogress错误;必须显式配置{w:"majority",j:true}、人工节流迁移、清理残留状态并统一journal硬件配置。

业务写入阻塞不是迁移命令本身造成的,而是源分片持续写入 + 目标分片追赶延迟 + 应用未适配 writeConcern 共同触发的连锁反应。直接调大超时或重启 mongos 不能解决问题。
为什么 moveChunk 期间会出现写入阻塞
根本原因在于写路径分裂:源分片照常接收写请求并记 oplog,目标分片在 catching_up 阶段批量回放这些变更。若此时应用仍用 { w: 1 } 写入,变更只落源分片,目标端尚未同步,mongos 在路由时可能返回陈旧数据;更严重的是,当目标分片 journal 刷盘慢(比如机械盘 + storage.journal.commitIntervalMs: 100),它每秒最多处理约 10 批 oplog,而源分片 SSD+30ms 间隔可处理 30+ 批——这种硬件级吞吐差会迅速堆积未确认操作,最终触发 ConflictingOperationInProgress 错误,表现为写入 hang 住或超时。
必须在应用层显式配置 writeConcern
mongos 不会自动升级客户端的 writeConcern,所有缓解都得从写入端入手:
- 对高一致性要求的集合(如患者主索引、订单状态),写入时强制指定
{ w: "majority", j: true }—— 这确保变更已在多数副本(含迁移中目标分片)的 journal 落盘才返回成功,天然拉长延迟、降低冲突概率 - 绝对避免
{ w: 0 }或{ w: 1 },这类配置会让写入“看不见”目标分片的存在,加剧读写分离失配 - 连接字符串中不带 writeConcern 参数?那就得在每个
insertOne、updateMany调用里显式传参,别依赖驱动默认值
迁移节奏必须人工节流,不能只靠均衡器
MongoDB 没有内置迁移限速开关,但你可以组合控制:
- 把
chunkSize设为1(单位 MB),单次迁移数据量小,moveChunk执行时间缩短,冲突窗口自然压缩 - 停掉均衡器:
sh.stopBalancer(),改用脚本逐个迁移:sh.moveChunk("db.coll", { _id: 1 }, "shard001"),每次调用后加sleep(5000)等待目标分片稳定 - 实时监控
config.migrations中"state": "catching_up"的数量,超过 3 个就暂停下一轮 —— 这说明目标分片已开始积压,再推新 chunk 只会让问题恶化
目标分片宕机后残留的迁移状态必须清理
如果目标分片挂了,config.migrations 里会卡住 "state": "committing" 或 "cloning" 的记录,后续任何 chunk 操作都可能失败。关键不是删得快,而是删得准:
- 连 config 副本集主节点(不是 mongos),执行:
db.migrations.deleteMany({"state": {$in: ["catching_up","cloning","committing"] } }) - 对已标记
"committed"但目标分片不可达的记录,先查db.chunks.findOne({min: ..., max: ...})确认该 chunk 仍在源分片上完整存在,再删 migration 记录 - 最后运行:
db.runCommand({cleanupOrphaned: "db.collection"})(替换为实际库和集合名),这个命令只清理元数据与物理数据不一致的情况,不会误删有效文档
最容易被忽略的是磁盘 I/O 和 journal 配置的硬件级不匹配——它比任何上层节流都更致命。迁移前务必确认源/目标分片使用同代存储介质,并统一 storage.journal.commitIntervalMs 值。否则,你调再细的 chunkSize、写再严的 writeConcern,也挡不住底层刷盘能力断层带来的阻塞。











