bulkwrite 比多次 updateone 更快因网络往返从 n 次减至 1 次,服务端可合并执行计划、复用查询缓存、批量加锁;但非自动优化器,写法不当反更慢。

bulkWrite 为什么比多次 updateOne 更快
因为网络往返次数从 N 次降到 1 次,MongoDB 服务端能合并执行计划、复用查询缓存、批量申请锁,尤其在高延迟网络或大集合上差异明显。但注意:它不是“自动优化器”,写法不对反而更慢。
- 单条
updateOne发起一次 TCP 请求;bulkWrite把所有操作打包成一个 BSON 文档发送 - 服务端对同集合的多个更新操作可能共用同一个索引扫描结果,而分散调用会重复走索引
- 如果混入大量不匹配的
filter(比如查不到文档),bulkWrite仍要解析每条指令,此时性能优势收窄甚至反转
updateMany 和 bulkWrite 在部分字段更新时的区别
updateMany 只能对整个匹配集统一应用同一套更新操作;bulkWrite 允许每条更新指令带独立 filter 和独立 update,这才是“部分更新”的核心能力。
- 想给 user_id=101 的文档加
status: "active",同时给 user_id=202 的加verified: true?必须用bulkWrite,updateMany做不到 -
updateMany的$set是静态的;bulkWrite中每个updateOne或replaceOne可以动态构造不同update对象 - 误把
updateMany当成“批量”来用,结果所有文档被塞进同一组字段——这是最常踩的语义坑
bulkWrite 中 updateOne 的 upsert 与 filter 写法陷阱
upsert 不是开关,它依赖 filter 是否能唯一标识目标文档;写错 filter 会导致意外插入、重复插入,甚至覆盖不该动的数据。
-
filter: { email: "a@b.com" }+upsert: true是安全的,前提是email有唯一索引 -
filter: { name: "Alice" }+upsert: true很危险:同名用户存在时,可能匹配多条,MongoDB 只随机选一条更新,其余忽略;没匹配到时又插入一条新文档 - 没建索引却开
upsert,写入量大时容易触发重复键错误duplicate key error collection - 建议始终搭配
{ upsert: true, collation: { locale: "en", strength: 2 } }防止大小写导致的重复插入
批量更新时如何避免 OOM 或超时
MongoDB 驱动默认不限制单次 bulkWrite 的操作数,但实际受内存、连接缓冲区、服务端 maxMessageSizeBytes(通常 48MB)限制。一口气传 10 万条,大概率失败。
- 客户端内存爆掉前,先收到
errmsg: "message too large"错误 - 推荐按 100–500 条/批切分;用
writeConcern: { w: "majority", j: true }时,批次越小,确认越快 - 别依赖
ordered: false来“跳过失败项”——它只是继续执行后续操作,错误仍会返回在result.writeErrors里,得自己遍历处理 - 真正影响吞吐的是磁盘 IO 和索引更新频次,而不是批大小本身;测试时优先观察
mongostat的netIn和flushes,而不是只看代码跑多快
最容易被忽略的是:bulkWrite 不保证原子性跨文档,哪怕在一个数组里传了 10 条 updateOne,其中第 3 条失败,前 2 条已生效——这和事务完全不同,别当成“批量事务”来用。










