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