Session 是事务的唯一上下文载体,所有操作必须显式传入同一 session 对象,否则脱离事务变为普通写入;跨 Model 操作需手动传递 session;不同 session 无法共享事务状态;session 生命周期须与连接就绪状态匹配。

Session 是事务的唯一上下文载体
MongoDB 事务不靠全局锁或中心协调器,而是把所有操作绑定到一个 session 对象上。这个对象里存着 lsid(逻辑会话 ID)、txnNumber(事务序号)、stmtIds(语句 ID 列表)等元数据——服务端靠它识别“哪些操作属于同一笔事务”。一旦换 session,就等于换了身份证,服务端完全无法关联前序操作。
漏传或混用 session 导致事务静默退化
常见错误是:调用了 session.startTransaction(),但后续 insertOne()、updateMany() 没传 { session } 参数。这类操作会直接脱离事务上下文,变成普通写入,且不会报错,只在数据不一致时暴露问题。
- 错误写法:
await usersCollection.updateOne({ _id: 1 }, { $inc: { balance: -100 } })—— 即使当前有活跃 session,也不进事务 - 正确写法:
await usersCollection.updateOne({ _id: 1 }, { $inc: { balance: -100 } }, { session }) - 跨 Model 操作(如 Mongoose)也必须显式传参:
await User.findOneAndUpdate(..., { session }),不能依赖“当前 session 自动注入”
不同 session 之间无法共享事务状态
事务的原子性、隔离性、回滚能力全部依赖 session 内部状态。比如快照隔离靠的是该 session 的 txnNumber 对应的 WiredTiger 快照;commit 或 abort 也只对当前 session 的 txnNumber 生效。两个 session 各自 startTransaction(),等于开了两个互不感知的事务窗口,哪怕操作同一集合,也无法形成原子单元。
由 ReMe 驱动的记忆管理系统,支持跨会话记忆持久化、自动应用用户偏好以及智能上下文压缩...
更隐蔽的问题是:在分片集群中,不同 session 可能路由到不同 mongos 或分片,事务协调器(shard router)根本无法聚合它们的操作日志。
session 生命周期与事务强绑定
事务必须在 session 存活期内完成,超时(默认 60 秒)后服务端强制终止,但客户端若没调用 endSession(),该 session 引用仍驻留内存,可能干扰后续连接复用。而 withTransaction() 封装自动处理了 session 创建、提交/回滚、endSession(),所以只要不手动复用 session 实例,就不会踩坑。
最容易被忽略的一点:Mongoose 中 mongoose.startSession() 必须在 mongoose.connection.readyState === 1 后调用,否则返回的 session 绑定的是未就绪拓扑,startTransaction() 会直接报 TopologyDescription not connected —— 这不是 session 用错了,是 session 根本没连上。

















