根本原因在于空集合分片时仅创建1个覆盖全键空间的chunk,导致所有初始写入集中到单一分片。预分片须在插入前完成,配合合理分片键与批量参数(如500–1000条/批、ordered:false)才能实现真正分布式写入。

根本原因不是分片本身拖慢了写入,而是默认的空集合分片行为让所有初始写请求都打到同一个分片上——相当于把分布式系统用成了单点写入。
为什么新分片集合会卡在第一个分片上
刚执行 sh.shardCollection() 的空集合,MongoDB 只创建 1 个初始 chunk,范围是整个分片键空间(如 {_id: MinKey} → {_id: MaxKey}),所有插入都路由到这个 chunk 所在的分片。即使你有 6 个分片,前几万条数据也全挤在一台机器上。
- 现象:mongostat 显示某分片的
insert和netIn暴涨,其他分片几乎为 0 - 后果:该分片 CPU、磁盘 I/O、网络带宽率先打满,后续 chunk 拆分(split)和迁移(moveChunk / moveRange)跟不上写入速度
- 关键限制:chunk 拆分需要满足大小阈值(默认 64MB)或文档数阈值,空集合插入小文档时,可能插入数万条都达不到拆分条件
预分片必须在插入前完成,且仅对空集合有效
一旦集合有任意一条文档,sh.splitAt() 或 sh.splitFind() 就可能失败或触发不均衡迁移;6.0+ 版本还禁用了 moveChunk 对空 chunk 的操作,只能靠 moveRange 或区域(zone)方式补救,但代价高、易出错。
- 安全窗口极短:从
sh.shardCollection()到第一条insert之间,是唯一能无风险预分片的时机 - 推荐做法:用
sh.updateZoneKeyRange()配合sh.addShardToZone()显式划分范围,再调用sh.shardCollection() - 反例:先插入再手动 split —— 容易产生“热点 chunk”,平衡器长期无法收敛,甚至卡死
分片键选择不当会让预分片失效
就算你预建了 100 个 chunk,如果分片键是单调递增的(如 timestamp、自增 id),新数据仍持续写入最后一个 chunk,等同于没分片。
- 哈希分片(
{field: "hashed"})可缓解,但牺牲范围查询能力,且不支持复合键哈希 - 范围分片更可控,但要求键值分布足够离散:比如用邮箱前缀
email: "a"~"z",比用created_at更易均匀 - 验证方法:插入后查
sh.status(),观察各分片的 chunk 数量和文档数是否接近
insertMany 配置不当会放大单分片压力
客户端一次发 5 万条进 insertMany(),即使路由正确,也会让目标分片瞬间积压大量写操作,触发锁竞争、内存溢出或超时。
- 必须控制单批大小:500–1000 条/批,尤其文档 > 1KB 时要往下调
- 禁用严格写关注:
writeConcern: {w: 1, j: false},避免等待 journal 刷盘拖慢节奏 - 设
ordered: false,防止某条失败阻塞整批,也减少事务协调开销 - 别忽略连接池:
maxPoolSize=20起步,否则 mongos 成瓶颈,所有请求排队等连接
最容易被忽略的是:预分片和批量写入参数是两套独立机制,只做一边效果有限。比如你预分了 20 个 chunk,但 insertMany 还是一次塞 2 万条,那每批仍会集中打向其中 1–2 个分片——并行度取决于「批次数 × chunk 分布宽度」,而不是 chunk 总数。











