Node.js中使用MongoDB事务必须先调用client.startSession()创建会话,所有操作需显式传入{session}选项,且事务仅支持副本集或分片集群,不跨数据库,推荐使用withTransaction()自动处理提交与回滚。

Node.js 里开启 MongoDB 事务必须用 startSession()
不调这个函数,后面所有 withTransaction() 或手动 commitTransaction() 都会报错:TopologyDescription not connected 或直接抛 TypeError: Cannot read property 'startTransaction' of undefined。它不是可选步骤,是硬性前置。
常见错误是直接在 db.collection('x').insertOne() 前加事务逻辑,忘了 session 是独立对象,得显式创建、传入、结束。
-
const session = client.startSession()必须在client.connect()之后调用 - 事务内所有操作(
insertOne、updateOne等)都得显式传入{ session }选项 - session 不能复用:一次事务结束后,不能再用同一个 session 对象开启新事务;要重用就得
session.endSession()后再client.startSession()
用 withTransaction() 比手动 commitTransaction()/abortTransaction() 更安全
手动控制容易漏掉 abortTransaction(),尤其在异步异常或未捕获的 Promise rejection 场景下,导致事务卡住、连接池耗尽,MongoDB 日志里会出现 transaction is already committed or aborted 这类误导性错误。
withTransaction() 内部自动处理 commit/abort 分支,也支持重试逻辑(默认 5 次),对网络抖动或临时冲突更鲁棒。
- 回调函数必须返回一个 Promise,否则事务不会等待就直接 commit
- 回调里抛出任何 error(包括
throw new Error()、Promise.reject()、未 catch 的 rejected promise)都会触发 abort - 不要在回调里调用
session.commitTransaction()—— 它会报Transaction already completed
await session.withTransaction(async () => {
await db.collection('orders').insertOne({ status: 'pending' }, { session });
await db.collection('inventory').updateOne(
{ sku: 'abc' },
{ $inc: { qty: -1 } },
{ session }
);
});
MongoDB 事务要求副本集或分片集群,单机 mongod 不支持
本地开发时如果只起一个 mongod --port 27017,执行事务会立刻失败,错误信息是:Transaction numbers are only allowed on a replica set member or mongos。这不是驱动问题,是服务端限制。
最轻量的解决方式是用 mongod --replSet rs0 --port 27017 启动,然后连上执行 rs.initiate()。不需要第二个节点,单节点副本集即可满足事务最低要求。
- 使用 Docker 的话,官方
mongo:latest镜像默认不启用副本集,需加--replSet参数启动 - 云数据库(如 MongoDB Atlas)默认开副本集,但免费层可能禁用事务,得确认集群版本 ≥ 4.0 且配置项
featureCompatibilityVersion是 4.0+ - 事务不跨数据库:所有操作必须在同一个
db实例下,不能db1.col.insert()和db2.col.update()混用
readConcern 和 writeConcern 影响事务可见性与持久性
默认情况下,事务内写入对其他客户端不可见,直到 commit;但事务自己读取时是否看到中间状态,取决于 readConcern。设成 "snapshot"(事务默认值)才能保证读已提交 + 可重复读。如果改成 "local",可能读到未提交的文档版本(虽不常见,但在某些并发路径下会暴露)。
writeConcern 决定事务 commit 是否等待多数节点落盘。默认 { w: 'majority' },但如果集群只有单节点副本集,w: 'majority' 实际等价于 w: 1,没额外保障。
- 事务开始前可通过
session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } })显式指定(withTransaction()内部已默认设好 snapshot) - 不要在事务中调用
db.runCommand({ getLastError: 1 })—— 它绕过事务上下文,结果不可靠 - 长时间运行的事务(> 60 秒)会被 MongoDB 自动终止,错误为
MaxTimeMSExpired,需提前拆解或加大maxTimeMS

















