跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

怎么在Node.js中处理MongoDB的事务

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
事务真正难的不是语法,是设计时想清楚“哪些操作必须原子,哪些可以降级为最终一致”。比如扣库存和创订单,如果库存服务和订单服务不在同一数据库,就得放弃事务,改用 Saga 模式。MongoDB 事务只解决单集群内的强一致性,不是银弹。

相关文章