mongodb事务需服务端≥4.0(副本集)或≥4.2(分片集群),驱动≥v1.4;必须用sessioncontext、同一database/collection实例;错误须按类型区分处理,不可盲目recover。

Go里用mongo.Client.StartSession开启事务的前提条件
事务不是开个session就能用的——必须确认MongoDB服务端版本 ≥ 4.0(副本集)或 ≥ 4.2(分片集群),且驱动用的是go.mongodb.org/mongo-driver/mongo v1.4+。旧版驱动调用StartSession不会报错,但后续WithTransaction会直接panic:「topology is not a replica set」。本地单节点测试时容易忽略这点,务必用rs.initiate()启副本集,而不是单纯mongod --port 27017。
mongo.SessionContext和普通context.Context混用会丢事务
事务生命周期完全绑定在mongo.SessionContext上,它由session.WithTransaction内部生成并透传。常见错误是把外部传入的ctx(比如HTTP请求的context.WithTimeout)直接塞进collection.InsertOne——这会导致操作脱离事务上下文,变成独立提交。
- 正确做法:所有数据库操作必须用
sessCtx(即session.WithTransaction回调函数参数) - 超时控制要设在
session.WithTransaction的options.TransactionOptions里,用SetMaxCommitTime - 别试图在回调外提前
session.EndSession(),驱动会自动清理
事务内读写必须用同一个mongo.Database和mongo.Collection实例
看起来冗余的操作其实很关键:事务不跨数据库,也不跨客户端。即使两个mongo.Collection指向同一集合,只要它们来自不同mongo.Database实例(比如一个从client.Database("a")取,另一个从client.Database("b")取),写入就会被拒绝,报错Transaction numbers do not match。
- 所有collection变量应在
session.WithTransaction回调开始时统一获取 - 避免在事务中调用封装了新
client.Database的工具函数 - 跨集合操作只支持同一数据库内,且所有集合必须已存在(事务不支持
CreateCollection)
事务失败后不能靠recover或重试逻辑兜底
MongoDB事务的原子性是服务端保证的,但Go驱动层的错误类型需要手动区分。比如writeConcernError(如多数节点不可达)和TransientTransactionError(如临时网络抖动)都可能触发重试,但UnknownTransactionCommitResult意味着服务端已提交、客户端却没收到响应——此时重试会导致重复执行。
- 检查错误是否实现
mongo.IsTransientTransactionError接口,仅对它做指数退避重试 - 遇到
mongo.CommandError且Name == "UnknownTransactionCommitResult",应记录日志并人工核对状态 - 不要用
defer func() { if r := recover(); r != nil { ... } }()捕获事务panic,它通常反映配置或逻辑错误,不是运行时异常
事务真正的复杂点不在代码长度,而在于错误分类和状态机判断——服务端返回的每个error code对应不同语义,漏判一种就可能让数据进入不确定状态。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










