Mongoose 6.0+ 默认支持 MongoDB 原生事务,但需在连接就绪(readyState === 1)后调用 startSession() 获取有效 session,且每个事务必须使用独立 session 并显式传入 { session } 选项;session.withTransaction() 自动处理重试与回滚,手动方式需严格调用 start/commit/abortTransaction() 并及时 endSession() 防止资源泄漏。

Mongoose 6.0+ 默认支持 MongoDB 原生事务,但必须显式创建并传入 session,否则 session.startTransaction() 会报错或静默失效。
为什么 session.startTransaction() 报 TopologyDescription not connected
这不是事务语法问题,而是 session 没绑定到可用连接。Mongoose 的 startSession() 必须在连接就绪后调用,且不能复用已结束的 session。
-
mongoose.connect()是异步操作,需等mongoose.connection.readyState === 1后再调用mongoose.startSession() - 每个事务必须使用独立
session,不能把同一个session多次startTransaction() - 若用
mongoose.connection.db.startSession()(原生 driver 方式),会绕过 Mongoose 的连接管理,易触发 topology 错误
session.withTransaction() 和手动 commitTransaction() 怎么选
withTransaction() 是封装好的错误自动回滚 + 重试逻辑,适合多数业务场景;手动控制适用于需要精细干预提交/回滚时机,或要捕获中间状态。
-
session.withTransaction(async () => { ... }):自动处理 transientTransactionError 重试(最多 5 次),失败时自动abortTransaction() - 手动流程必须严格按顺序:
session.startTransaction()→ 操作 →await session.commitTransaction()或await session.abortTransaction() - 手动方式下,未
commit或abort的 session 会占用资源,超时(默认 60 秒)后被服务端强制终止,但不会释放客户端引用
跨 Model 写操作必须共用同一个 session
Mongoose 的 save()、deleteOne()、updateMany() 等方法只有显式传入 { session } 选项才会纳入当前事务上下文。
- 错误写法:
await User.create({...})—— 不进事务,即使有活跃 session - 正确写法:
await User.create({...}, { session })或await user.save({ session }) - 注意:
Model.insertMany()支持{ session },但bulkWrite()的每个操作都得单独加session字段(如{ updateOne: { filter, update, session } }) - 查询操作(
find、findOne)加{ session }可读取事务中未提交的数据,用于“读己之所写”,但不强制要求
事务里调用 aggregate() 或 countDocuments() 的兼容性
MongoDB 4.2+ 支持事务内聚合管道(含 $lookup、$facet),但 Mongoose 的 Model.aggregate().session(session) 必须链式调用,且不能混用非事务集合。
-
await User.aggregate([...]).session(session).exec()✅ 有效 -
await User.countDocuments(filter, { session })✅ 支持(Mongoose 6.7+) - ⚠️
Model.findOne().session(session)在事务中返回的是快照数据,但若管道里用了$out或$merge,会直接报错:不支持事务内写入到其他集合 - 事务内禁止跨分片写入(sharded cluster 下需确保所有操作落在同一分片键范围内)
真正容易被忽略的是 session 生命周期管理:它不是 request-scoped 自动清理的,你得确保每次事务结束后调用 session.endSession(),否则连接池里的 session 对象会堆积——尤其在高并发短事务场景下,可能触发 “Too many sessions” 服务端限制。










