MongoDB事务内不能写if判断,因为事务只保证操作原子性,不支持服务端脚本执行;if语句在驱动层(如Node.js)运行,已脱离事务上下文,无法回滚,且两次网络往返破坏原子性。

MongoDB 4.0+ 原生支持多文档事务,但“带条件判断的复合原子更新”不能靠 if/else 逻辑实现——事务内不允许执行条件分支语句,必须把业务逻辑前置或转为 updateOne 的 $cond + $set 表达式,或拆成“读-判-写”三步并用 findAndModify + 唯一索引兜底。
为什么不能在事务里写 if 判断?
MongoDB 事务(session.startTransaction())只保证操作的原子性、一致性、隔离性和持久性,不提供服务端脚本执行能力。你在 db.collection.updateOne() 里传入的 filter 是匹配条件,不是运行时逻辑;事务块中调用 if (doc?.status === 'pending') {...} 这类 JS 判断,实际发生在驱动层(Node.js/Python),早已脱离事务上下文,无法回滚。
- 事务内部只能调用支持会话的写操作:如
updateOne、deleteOne、insertOne,且所有操作必须显式绑定session - 任何
collection.findOne()后再决定怎么写的模式,天然破坏原子性:两次网络往返间数据可能被其他客户端修改 - 驱动层的
if判断结果若用于构造后续 update,该 update 是否生效取决于 filter 是否命中,而非“判断逻辑”本身
用 $cond + $set 实现字段级条件更新(单文档内)
当条件仅涉及当前文档字段(如“若 status 是 pending,则改为 processing,否则不改”),直接在 updateOne 的 $set 中用聚合表达式处理,无需读取再判断。
db.orders.updateOne(
{ _id: ObjectId("..."), status: "pending" },
{
$set: {
status: { $cond: { if: { $eq: ["$status", "pending"] }, then: "processing", else: "$status" } },
updated_at: new Date()
}
},
{ session }
)
- 这个操作是原子的:匹配 + 更新一步完成,
status字段不会因并发写入出现中间态 - 注意
filter中已限定status: "pending",$cond是冗余防护,防止误更新;若想“匹配任意文档但只改特定状态”,就把filter放宽(如只用{_id: ...}),全靠$cond控制赋值 - 不支持跨字段复杂逻辑(如 “若 amount > 1000 且 user.tier === 'vip'”,需确保
user.tier存在,否则$eq返回false)
多文档条件更新:用 findAndModify + 唯一约束兜底
当需要“查 A 文档状态 → 决定是否更新 B 文档”,又要求整体原子性,MongoDB 没有跨文档条件事务。可行路径是:用 findAndModify 一次性读取并标记 A(如加 locked_by: sessionId),再在同一个事务中更新 B;同时为 A 的 locked_by 字段建唯一索引,确保并发时只有一个客户端能成功锁定。
- 先确保集合有唯一索引:
db.orders.createIndex({ "locked_by": 1 }, { unique: true }) -
findAndModify必须带new: true和upsert: false,且query包含业务条件(如{ status: "pending", locked_by: null }) - 若返回
null,说明没抢到锁,可重试或放弃;若返回文档,说明已成功“条件读取并加锁”,此时再在事务中更新 B 文档 - 务必在事务结束前清除
locked_by(或设 TTL),否则死锁
最容易被忽略的坑:session 生命周期和错误处理
事务失败后,session 不会自动关闭,未结束的事务会持续占用资源,直到超时(默认 60 秒)或手动 abortTransaction()。而很多驱动示例只演示了 commitTransaction(),漏掉异常分支的清理。
- 必须在
try/catch/finally中管理 session:无论成功或抛错,finally里检查session.inTransaction()并调用abortTransaction() - 网络中断、主节点切换等场景下,
commitTransaction()可能抛出UnknownTransactionCommitResult,此时需查transactionState确认是否已提交,不能简单重试 - 事务内所有操作必须使用同一
session,混用不同 session 或无 session 的操作,将脱离事务边界


















