事务必须在有效会话中启动,未调用startsession()直接withtransaction()将静默跳过;需先获取iclientsession再传入事务回调,所有操作必须显式传入该会话实例,否则脱离事务上下文导致数据不一致。

事务必须在有效会话中启动
没调 StartSession() 就直接 WithTransaction(),事务根本不会执行,也不会报错——它只是静默跳过。驱动要求所有事务操作必须绑定到一个显式创建的 IClientSession 实例。
正确做法是先获取会话,再传入事务回调:
var session = client.StartSession();
await session.WithTransactionAsync(async s =>
{
var db = client.GetDatabase("test");
var col = db.GetCollection<user>("users");
await col.InsertOneAsync(s, new User { Name = "Alice" });
await col.DeleteOneAsync(s, Builders<user>.Filter.Eq(x => x.Name, "Bob"));
});</user></user>
- 会话对象
session必须由当前MongoClient创建,跨客户端复用会抛InvalidOperationException -
WithTransactionAsync内部自动处理提交/回滚,但前提是回调里所有操作都显式传入该会话(如InsertOneAsync(s, ...)) - 别在事务块外调用
session.EndSession()——WithTransactionAsync会自动释放
事务内不能混用会话和非会话操作
常见错误:在事务回调里调用未带会话参数的 Find() 或 InsertOneAsync(),这些操作会走默认连接池,脱离事务上下文,导致数据不一致。
所有读写必须显式传入会话实例:
- ✅ 正确:
col.Find(s, filter)、col.UpdateOneAsync(s, filter, update) - ❌ 错误:
col.Find(filter)、col.InsertOneAsync(doc)(漏掉s参数) - 聚合操作也一样:
col.Aggregate(s).Match(...),不是col.Aggregate(...)
事务超时与写关注必须显式配置
默认事务最多运行 60 秒,超时后自动中止并回滚。如果业务逻辑较重(比如批量更新+外部 API 调用),必须通过 TransactionOptions 调整:
var options = new TransactionOptions(
readConcern: ReadConcern.Local,
writeConcern: WriteConcern.WMajority,
maxCommitTime: TimeSpan.FromMinutes(5)
);
await session.WithTransactionAsync(..., options);
-
maxCommitTime是硬限制,超过即终止,不是“建议等待时间” - Atlas 集群强制要求
WriteConcern.WMajority,本地单节点可设为WriteConcern.W1 - 别依赖
retryWrites=true参数——事务内重试由驱动自动管理,该参数只影响普通写操作
事务失败时的异常类型和重试逻辑
事务中途出错(如唯一键冲突、网络中断)会抛 MongoCommandException 或 MongoWriteException,但 WithTransactionAsync 默认**不重试**——它只保证原子性,不保证幂等性。
需要重试必须手动封装:
- 驱动不自动重试事务,哪怕错误是临时性的(如主节点切换)
- 若要实现指数退避重试,需在外层捕获异常并循环调用
WithTransactionAsync - 注意:重试时必须新建会话,旧会话已失效,重复使用会抛
ObjectDisposedException
最易被忽略的是事务内调用外部服务(如发邮件、调支付接口)——这些操作无法回滚,必须设计成幂等或补偿机制,MongoDB 本身管不了它们。











