bulkWrite比insertMany更可控,因其支持混合操作、细粒度writeConcern与upsert配置,并可通过ordered:false实现无序并行执行,避免单点失败中断及写入瓶颈。

bulkWrite 为什么比 insertMany 更可控
bulkWrite 是 MongoDB 4.2+ 的底层批量写入接口,insertMany 实际是它的封装糖。区别在于:bulkWrite 允许混合操作(插入、更新、删除)、精确控制每条操作的 writeConcern 和 upsert 行为,还能通过 ordered: false 触发无序并行执行——这对副本集尤其关键,因为 mongos 或主节点可将不同操作分发到多个从节点 oplog 队列中,避免单点写入瓶颈。
常见错误是直接把 insertMany 当作“批量”用,却没意识到它默认 ordered: true,一旦某条失败,后续全部中断;而 bulkWrite 在 ordered: false 下即使部分失败,其余仍继续,且驱动层能更早释放连接。
- 批量大小建议控制在 100–500 条/批:太小起不到减少网络往返的作用,太大可能触发 WiredTiger 内存压力或超时
- 务必显式传入
{ writeConcern: { w: 1 } }:副本集默认就是w: 1,但不显式写容易被误认为依赖默认,实际部署中可能被配置覆盖 - 避免在
bulkWrite中混用高代价操作:比如一个 batch 里既有updateOne(需先查再改)又有insertOne,会拉长整体耗时;尽量按操作类型分批
副本集里 writeConcern 设置不当的典型表现
副本集写入慢,80% 情况下不是磁盘或 CPU 瓶颈,而是 writeConcern 卡在等待多数节点确认。比如设置 w: "majority",但某个从节点延迟高或网络抖动,整个 bulkWrite 就卡住直到超时(默认 30 秒)。
真实场景中,日志类、埋点类数据通常不需要强一致性,这时应降级为 w: 1;而订单类关键数据才用 w: "majority"。别全局统一配,要按 collection 甚至按操作粒度区分。
-
w: 0不推荐:连主节点都不等确认,网络丢包或进程崩溃就真丢数据,不是“快”,是“赌” -
w: 1是平衡点:只等主节点落盘,WiredTiger 的 journal 已保证崩溃恢复,延迟通常 - 如果用了
w: "majority",必须确保所有从节点 oplogSize 足够大(至少容纳 24 小时变更),否则从节点追不上主节点,写入会持续阻塞
WiredTiger 缓存刷新频率对大批量写入的实际影响
WiredTiger 默认每 100ms 刷一次 journal,每秒刷一次数据文件。大批量写入时,频繁刷盘会导致 I/O 毛刺,尤其在 HDD 或低配云盘上。但盲目调大刷新间隔风险极高——journal 是崩溃恢复的唯一依据,间隔超过 100ms 就可能丢失最近写入。
真正该调的是 cache 大小和压缩策略,而不是刷盘频率。比如 --wiredTigerCacheSizeGB 4 可让热数据常驻内存,减少磁盘读;用 --wiredTigerCollectionBlockCompressor zlib 替代默认 snappy,虽然压缩慢一点,但写入后体积更小,长期看反而降低 I/O 压力。
- 不要碰
journalCommitIntervalMs:官方明确不支持运行时修改,强行改可能导致 journal 文件损坏 - 监控
db.serverStatus().wiredTiger.cache中的bytes currently in the cache和maximum bytes configured比值,低于 70% 就说明 cache 不够,写入时频繁换页 - 如果用的是云数据库(如 Atlas、阿里云 MongoDB),这些参数通常不可调,那就只能靠前置批量 + 合理 writeConcern 来扛
分片集群下 bulkWrite 的路由陷阱
在副本集基础上加了分片,bulkWrite 的行为会变复杂。mongos 不会把一个 bulk 请求拆成多路并发发给所有分片;它只根据分片键 hash 或范围,把每条操作路由到对应分片——所以如果批量里所有文档的分片键都落在同一个 chunk 上,实际还是单分片扛压。
典型例子:用时间戳做分片键,又集中写入当天数据,结果所有写请求打到同一个分片,其他分片闲着。这不是 bulkWrite 的问题,是分片键设计缺陷。
- 测试分片分布:写入前用
sh.status()看各分片 chunk 数量是否均衡 - 避免单调递增分片键:比如
_id: ObjectId或timestamp,改用哈希索引(sh.shardCollection("db.coll", { _id: "hashed" }))或业务 ID 打散 - 预拆分空集合:对已知写入量大的集合,在
shardCollection后立刻用sh.splitAt()手动切好 chunk,避免初始全在一块上
批量写入性能优化的核心不在“怎么写快”,而在“不让不该等的地方等”。副本集里最常被忽略的,是 writeConcern 和分片键的耦合效应——一个设错,另一个再怎么调 bulkWrite 参数也白搭。











