MongoDB分片集群写入吞吐量上不去?别急着调整连接数或修改writeConcern,问题可能根本不在这些方面。根据实践经验,“预分片 + bulkWrite + ordered: false”才是真正能够释放分片集群写入吞吐量的唯一组合。其他优化手段在默认单chunk结构下,顶多是掩耳盗铃——看似忙碌,实则一个瓶颈都没解决。
预分片+bulkWrite+ordered:false是释放分片集群写入吞吐量的唯一有效组合。因为刚分片的空集合默认只有一个chunk,所有写入都会集中在单一分片上,必须提前进行预分片,并配合无序批量写入,才能实现真正的并行写入。

为什么刚分片的集合导入会卡死在第一个shard
对空集合做首次分片时,MongoDB默认只创建1个chunk,范围覆盖整个分片键空间。所有写请求都会路由到持有这个chunk的shard上——哪怕你启用了16个shard,99%的写入压力仍然压在其中一个。典型症状:sh.status()里看到chunks行只有1条,shard列全为shard0000。
有人会问:balancer不是会自动拆分吗?注意,balancer只负责迁移已有的chunk,并不会自动“生长”新的chunk。等数据写满128MB触发自动split时,迁移早已滞后,写入队列持续堆积。所以,等它自动平衡,黄花菜都凉了。
预分片必须在写入前完成,且只对空集合生效
一旦集合里存在任意一条文档,sh.splitAt()大概率会失败,sh.updateZoneKeyRange()也无法覆盖未初始化的键空间。补救操作极易引发块分布倾斜或迁移卡死。因此,预分片必须在写入前完成,且只对空集合生效。
- 哈希分片键(如
{_id: "hashed"})最稳妥:切点可以精确计算出来,例如8个shard对应7个切点,用NumberLong("0x2000000000000000")这类值 - 范围分片键(如
{email: 1})必须使用sh.updateZoneKeyRange()配合sh.addShardToZone(),且sh.shardCollection()必须最后执行 - 切完后立刻验证:
sh.status()中chunks总数应等于预设数量,且每个shard至少分配到1个chunk
客户端必须用bulkWrite并设ordered: false
预分片只是铺好了路,如果客户端仍然串行发送insertOne或使用mongoimport,mongos仍可能把请求扎堆打到同一个shard。只有bulkWrite开启无序模式,才能触发真正的并行路由。
mongoimport本质是单文档循环插入,不支持ordered: false,千万级导入比bulkWrite慢3–5倍- PyMongo示例:
collection.bulk_write(operations, ordered=False),Node.js同理 - 错误处理要自己检查返回里的
writeErrors字段,无序模式下失败不影响其余操作
最容易被忽略的细节:分片键选择决定预分片是否真正有效
即使你成功预分了100个chunk,若分片键是单调递增的created_at或自增_id,新数据仍会持续写入最后一个chunk,导致热点回退——这和没预分片几乎没有区别。
哈希分片键天然能够规避该问题;范围分片键必须确保业务数据在键值上均匀分布(比如用email前缀而非完整邮箱),否则预分片只是徒劳。这一点,往往是最容易被忽略的暗坑。
