MongoDB 7.0 中事务不支持 bulkWrite(),必须手动拆解为带 session 的单操作(如 updateOne());upsert 不再自动重试 DuplicateKeyError,需显式判断或改用 replaceOne()。

事务里不能直接用 bulkWrite()
在 MongoDB 7.0 中,bulkWrite() 本身不支持在事务中执行——它会抛出 MultiDocumentTransactionNotSupported 错误。事务只接受单个操作(如 updateOne()、insertOne())并要求显式传入 session。批量逻辑必须由你手动拆解,而不是交给 bulkWrite() 自动分组。
常见错误现象:在事务回调里调用 collection.bulkWrite(ops, { session }),结果报错:Command not supported in transaction: bulkWrite。
- 事务中所有写操作必须单独调用,且每个都带
session参数 -
bulkWrite()是客户端批量协调机制,与事务的原子性保证机制冲突 - 即使操作目标是同一集合,也不能绕过这个限制
用 for 循环 + updateOne() 在事务中安全批量更新
最直接可靠的做法:把批量更新拆成多个 updateOne() 调用,全部塞进事务回调函数里。MongoDB 7.0 对事务内操作数量没有硬性上限,但要注意单次事务大小不能超过 transactionTooLargeForCache 限制(默认约 50MB 内存占用)。
示例场景:给 200 个用户状态设为 "archived",条件是 _id 在指定数组中:
session.withTransaction(async () => {
const userIds = [ObjectId("..."), ObjectId("...")];
for (const id of userIds) {
await db.users.updateOne(
{ _id: id },
{ $set: { status: "archived", updatedAt: new Date() } },
{ session } // ← 每次都必须传
);
}
});
- 不要用
updateMany()—— 它在事务中可用,但无法保证“每个文档更新成功与否独立可见”,失败会中断整个事务 - 如果需 upsert,用
updateOne({ ... }, { $set: ... }, { upsert: true, session }) - 循环体内部避免 await 外部异步调用(比如查表、HTTP 请求),否则事务锁持有时间拉长,易超时
unorderedBulkOp 也不能进事务
initializeUnorderedBulkOp() 和 initializeOrderedBulkOp() 都不支持传 session,它们底层仍走非事务协议。试图强行注入 session 会导致 InvalidOptionsError: session is not a valid option 或静默忽略 session 导致数据不一致。
容易踩的坑:
- 误以为 “bulk” 就等于 “高效”,在事务里硬套 bulk 接口,结果既没提速又破坏事务语义
- 用 bulk 初始化后调
execute({ session })—— 这个session参数会被忽略,操作实际在事务外执行 - 混淆了 mongosh 的交互式 bulk 和驱动程序中的事务 API,前者纯客户端模拟,后者需服务端协同
7.0 特别注意:upsert 不再自动重试
MongoDB 7.0.22+ 明确取消了事务中 upsert 操作对 DuplicateKeyError 的自动重试行为。这意味着:如果你在事务里用 updateOne(..., { upsert: true }) 更新一个已存在唯一键的文档,它会直接失败,不会像旧版本那样尝试替换。
应对方式:
- 先
findOne()判断是否存在,再决定调updateOne()还是insertOne() - 或改用
replaceOne()+upsert: true,它在事务中行为更稳定 - 捕获
DuplicateKeyError后手动处理,而不是依赖驱动重试逻辑
真正关键的不是“怎么批量”,而是“事务里每个操作都得亲手喂 session,且不能假定任何批量捷径”。批量只是手段,事务一致性才是目的——这点在 7.0 里比以前更刚性。

















