预分片空集合这事儿,听着可能有点抽象,但它确实是提升批量插入吞吐量最直接有效的手段。说到底,原理就是提前把分片键空间切分成多个chunk,均匀分配到各个分片上,避免默认单chunk导致的单点写入瓶颈。不过得注意,它只对空集合管用,而且chunk数量、大小、分片键选择都得合理。

注意事项也很明确:预分片必须在数据写入前完成,一旦集合里有数据,再手动执行 split 或 moveRange,很容易导致块分布不均、迁移卡顿,甚至让平衡器罢工。这一点很多初次接触的人容易踩坑,值得提前重视。
为什么预分片能提升插入吞吐量
默认情况下,首次给空集合分片时,MongoDB 只建一个初始 chunk,所有写请求一股脑全压到同一个分片上,单点瓶颈就这样形成了。预分片相当于提前把整个分片键空间切成多个 chunk,均匀铺到各分片上——后续插入自然就具备了并行能力。
这里有两个关键点:第一,只有空集合才能安全预分片;第二,从 6.0+ 版本开始,moveChunk 不再支持空范围迁移,得用 moveRange,或者通过 sh.addShardToZone + sh.updateZoneKeyRange 配合分片操作来触发自动分配。
另外还有几个容易忽视的细节:
- chunk 数量太少 → 写入集中,CPU/网络先扛不住
- chunk 太大(比如超过 128MB)→ 后续迁移耗时,影响平衡器调度
- 分片键选得不合理(比如单调递增)→ 哪怕预分了片,新数据照样只往最后一个 chunk 里灌
用 sh.updateZoneKeyRange 配合区域定义预分片
这是目前最可控、兼容性最好的方式,尤其适合范围分片场景(比如 email、created_at)。它不依赖 shell 函数,可以脚本化部署,还能避免手写 split 时边界写错的问题。
举个例子:为 sample.documents 在 email 字段上预建 10 个等宽范围,绑定到 3 个分片:
sh.addShardToZone("shard01", "zone-east")
sh.addShardToZone("shard02", "zone-west")
sh.addShardToZone("shard03", "zone-central")
// 定义 10 个 email 前缀范围,覆盖 aa ~ zz
sh.updateZoneKeyRange("sample.documents", { email: "aa" }, { email: "az" }, "zone-east")
sh.updateZoneKeyRange("sample.documents", { email: "az" }, { email: "bz" }, "zone-west")
// ... 继续定义其余范围,确保 maxkey 覆盖完整空间
sh.shardCollection("sample.documents", { email: 1 })
注意顺序:sh.shardCollection 必须在所有 updateZoneKeyRange 执行完毕之后再调用,否则没覆盖到的键空间会被自动分配成默认 chunk,预想的结构就乱了。
moveRange 手动分配空 chunk 的实操要点
如果没办法提前定义区域,或者需要动态调整 chunk 分布,moveRange 是目前唯一推荐的手动方式(替代了已弃用的 moveChunk 空范围操作)。它会自动完成 split + move,但前提是目标分片已在集群中且状态健康。
- 命令格式:
sh.moveRange("sample.documents", { email: "aa" }, { email: "az" }, "shard01", "shard02") - 源分片和目标分片名必须准确,大小写敏感
- 执行前先确认
sh.status()中没有任何正在运行的迁移任务,否则会排队阻塞 - 绝对不要对已含数据的范围执行
moveRange——它不校验数据一致性,可能引发静默丢失
一个典型误操作:在非空集合上反复 moveRange 同一区间,会导致 chunk 元数据混乱,config.chunks 中间出现重叠或空洞,最终平衡器直接拒绝调度。
容易被忽略的三个硬约束
预分片不是越多越好,实际效果受三层底层机制限制:
- 每个分片默认最多承载 256,000 个 chunk,超出后
mongos会拒绝新 chunk 创建请求,报错ChunkTooBig或CannotCreateChunk - 分片键值必须可比较且能线性排序。
ObjectId没问题,但含嵌套对象或数组的字段不能用作范围分片键 - 所有预定义范围的
max必须严格等于下一个范围的min,中间一旦出现空隙,系统会自动补 chunk,你预期的分布就白搭了
说到底,决定吞吐上限的根本不是 chunk 数量,而是分片键的离散度与写入模式是否匹配。哪怕你预分了 1000 个 chunk,如果 90% 的插入都落在前 3 个 chunk 里,性能照样卡死在单分片上。
