事务在mongoose中需显式管理session并每步传参,漏掉session.starttransaction()或任一{session}选项即失效;startsession()须待连接就绪(readystate===1)后调用,withtransaction()内须主动throw错误才能回滚,findoneandupdate()必须带{session,returndocument:'after'}。

事务在 Mongoose 中不是“开个开关就自动生效”的功能,它依赖显式 session 管理和每一步操作的精确传参;漏掉 session.startTransaction() 或任意一次 { session } 选项,事务就形同虚设。
session.startTransaction() 报 TopologyDescription not connected 怎么办
这不是语法错,是 session 没绑定到可用连接。Mongoose 的 startSession() 必须等连接就绪后才能调用,否则返回的 session 是“空壳”。
-
mongoose.connect()是异步的,必须监听mongoose.connection.readyState === 1或用await mongoose.connection.on('connected', ...)确保连接完成后再调用mongoose.startSession() - 不能复用已
endSession()的 session,也不能把同一个 session 多次调用startTransaction() - 避免直接用
mongoose.connection.db.startSession()—— 它绕过 Mongoose 连接池管理,容易触发 topology 错误
withTransaction() 回滚失败,为什么 throw 了也不回滚
withTransaction() 不会自动捕获业务异常,它只响应你主动 throw 的错误。如果在回调里用了 try/catch 却没重新 throw,事务就静默提交了。
- 所有数据库操作必须放在
withTransaction(async () => { ... })的回调内,不能提前做 HTTP 请求、文件读写等副作用操作 - 回调必须返回 Promise;校验失败时直接
throw new Error('xxx'),不要return Promise.reject(...)(它不被识别为“抛出”) - 默认只对
TransientTransactionError自动重试(最多 5 次),ValidationError或网络超时不会重试;如需自定义,传{ maxRetries: 2, delayMs: 100 }
findOneAndUpdate() 在事务里返回 null 或报 session ended
该方法底层可能拆成 find + update 两步,若没传 { session },第二步就脱离事务上下文,导致状态不一致或 session 提前关闭。
- 必须显式加
{ session, returnDocument: 'after' }—— 后者确保返回更新后的文档,避免读到旧值 - 不能只传
{ session }就完事;returnDocument: 'after'是安全读取的关键,否则可能读到事务外的脏数据 - 注意:
findOneAndUpdate()不支持new: true选项,必须用returnDocument替代
跨 Model 操作必须共用同一个 session 实例
Mongoose 不会自动把多个 Model 的操作“聚到一个事务里”,哪怕它们用的是同一个数据库。每个操作都得手动传入同一个 session 对象。
-
User.create(..., { session })和Order.updateOne(..., { session })必须用同一个session实例,否则就是两个独立事务 -
Model.insertMany()支持{ session };但bulkWrite()的每个子操作(如{ updateOne: { filter, update, session } })都要单独加session字段 - 查询操作(
find()、findOne())加{ session }才能读到本事务中尚未提交的变更(即“读己之所写”)
最容易被忽略的是:事务内所有操作必须严格使用同一 session 实例,且每次调用都显式传参——没有隐式上下文,也没有“默认 session”这回事。漏掉任意一处 { session },就等于那步操作根本没进事务。











