sh.movechunk 不能并发执行,因内部 movechunklock 强制串行;但可绕过均衡器,通过多个 mongos 实例对互斥 range 手动并发调用。

sh.moveChunk 能否并发执行?不能,但可绕过
直接调用 sh.moveChunk() 是串行的:同一时间只能迁移一个 chunk,mongos 会阻塞后续调用直到前一个完成。这不是限制在命令层面,而是由内部锁(如 MoveChunkLock)强制保证的——防止跨分片状态不一致。
真正能提升效率的方式是绕过均衡器,手动触发多个 sh.moveChunk(),但必须满足两个前提:
- 目标分片之间无 chunk 边界重叠(即每个迁移的
{ shardKey: value }区间互斥) - 所有操作通过不同 mongos 实例发起(或至少不同连接会话),避免单个 mongos 的串行排队
例如,你有 3 个 mongos 进程,可以同时对三个不相交的 range 执行:
sh.moveChunk("db.col", { uid: 1000 }, { to: "shardA" })
sh.moveChunk("db.col", { uid: 2000 }, { to: "shardB" })
sh.moveChunk("db.col", { uid: 3000 }, { to: "shardC" })
注意:若区间重叠(比如都用 { uid: { $gte: 1000 } }),mongos 会拒绝并报错 conflicting moveChunk operations。
为什么停用 Balancer 反而能加速迁移?
启用均衡器时,sh.moveChunk() 实际被包装进后台任务队列,受 balancer.maxConcurrentMigrations 控制(6.0 默认为 2)。即使你手动调用,它也会被节流,且无法跳过 chunk 边界校验、预检查、日志记录等冗余流程。
停用后执行迁移,本质是“直连分片”,跳过 mongos 的协调层,关键优势包括:
- 不等待 balancer 周期性扫描(默认每 10 秒一次)
- 绕过自动 split 判断逻辑(避免因 chunk 太大而先触发 split 再 move)
- 可精确控制 chunk 边界,避免均衡器按“大小优先”策略拆出大量小 chunk
操作顺序必须是:sh.setBalancerState(false) → sh.moveChunk(...) → 确认成功后再 sh.setBalancerState(true)。中间若崩溃,需人工清理 config.migrationHistory 中残留状态。
chunk 迁移慢的常见瓶颈在哪?
不是网络带宽,也不是磁盘 I/O,而是迁移过程中的写放大和锁竞争。6.0 默认启用 snappy 压缩,但压缩本身不减少 chunk 写入量——它只压缩 wire protocol 流量,而 chunk 数据仍需在源分片删除、目标分片插入、oplog 同步三阶段全量落盘。
以下三点最容易被忽略:
-
moveChunk期间,该 chunk 对应的所有写操作会被阻塞(读不受影响),若 shard key 高频更新,会导致迁移卡在“waiting for writes to drain”状态 - 目标分片若正在做
compact或大量索引构建,moveChunk会等待其完成,表现为长时间停留在 “cloning” 阶段 - 若使用哈希分片键,chunk 数量远多于范围分片,迁移总次数翻倍,但单次更轻;范围分片则相反——要提前用
sh.status()查看 chunk 分布,避免盲目迁移
如何验证迁移是否真的变快了?
别只看 sh.moveChunk() 返回时间,它只代表命令提交成功。真实耗时要看分片日志和指标:
- 查源分片日志:搜索
"moveChunk commit"和"moveChunk done"时间差 - 查目标分片
serverStatus().metrics.operation.moveChunk.bytesSent,对比迁移前后网卡流量突增是否匹配(确认 snappy 是否生效) - 运行
db.currentOp({"secs_running": { $gt: 30 }, "msg": /moveChunk/}),观察是否有操作卡在"waiting for destination to catch up"
最有效的提速方式,其实是减少迁移总量:通过 sh.splitAt() 预切好 chunk,再用 sh.moveChunk() 按需搬运,比依赖 balancer 自动发现+迁移快 3–5 倍。这要求你对数据分布有明确预期,而不是把调度权完全交给后台线程。











