Node.js中MongoDB事务必须通过withSession()创建会话并用withTransaction()执行,所有操作需显式传入{session}选项,仅支持副本集或分片集群,不跨数据库,失败自动回滚。

事务必须在会话中执行,withSession() 是入口
Node.js 驱动不支持直接在 client.db().collection() 上调用事务方法。所有事务操作必须包裹在显式会话中,而 withSession() 是启动会话并确保资源自动清理的最简方式。它会自动处理会话生命周期:无论事务成功或失败,会话都会被正确结束。
常见错误是跳过 withSession()、直接在客户端上调用 startTransaction() —— 这会导致会话泄漏,且后续事务可能因因果一致性缺失而读不到自己刚写的数据。
- 必须传入
async (session) => { ... }回调,不能传同步函数 - 会话对象不可复用;每次事务都应新建会话(
withSession()内部已保证) - 如果应用需要跨请求复用会话(如 Web 请求链路),需自行管理生命周期,但绝大多数场景下不应这么做
withTransaction() 封装原子性逻辑,失败自动回滚
withTransaction() 是真正执行事务的 API,它接收一个异步回调,并在其中所有操作成功时提交,任一 Promise reject 或抛出异常时自动中止并回滚。你不需要手动调用 commitTransaction() 或 abortTransaction()。
注意:这个方法只在 session 对象上调用,不是 client 或 db;且它本身返回一个 Promise,必须 await。
- 回调函数内所有数据库操作(
insertOne、updateOne、findOne等)都必须显式传入{ session }选项 - 未传
session的操作不会参与事务,也不会被回滚 —— 这是最常见的静默数据不一致来源 - 事务内读操作默认使用
snapshot读关注(v6.0+),能保证读取到事务开始前的统一快照,避免幻读
事务要求 MongoDB Server ≥ 4.0,且必须是副本集或分片集群
单节点 MongoDB 实例(即非副本集模式)无法启用事务。即使代码完全正确,withTransaction() 也会立即 reject 并抛出 MongoError: Transaction numbers are only allowed on a replica set member or mongos。
开发时容易忽略这点:本地 mongod --port 27017 启动的是单机模式,必须改用 mongod --replSet rs0 并初始化副本集(哪怕只有一个成员)。
- 检查是否为副本集:连接后运行
rs.status(),有输出即为副本集;否则需初始化:rs.initiate() - 连接字符串必须包含
?replicaSet=rs0参数,例如mongodb://localhost:27017/mydb?replicaSet=rs0 - 事务对性能有开销:每个事务会持有会话锁和 snapshot 锁,长事务会阻塞其他写入,建议控制在 60 秒内完成
错误处理要分层:事务外捕获连接/会话错误,事务内处理业务逻辑异常
withSession() 可能因网络、认证、权限等问题失败;withTransaction() 内部可能因唯一键冲突、库存不足等业务原因 reject。这两类错误应分开处理。
典型陷阱是把所有 catch 堆在最外层,导致无法区分“根本连不上数据库”和“库存不够不能下单”——前者该告警,后者该返回用户友好提示。
- 在
withSession()外层try/catch捕获初始化会话失败(如 DNS 解析失败、TLS 握手超时) - 在
withTransaction()回调内部用try/catch捕获可预期的业务异常(如if (stock ) - 不要在事务回调里
console.error后吞掉错误;驱动依赖 rejection 触发回滚,静默 catch 会导致数据半提交
findAndModify 或写关注 + 重试机制补足,不是开启事务就能自动规避的。



















