必须用 mongo.WithSession,因为它自动处理事务提交或中止、defer 清理会话,并防止 panic 或超时导致事务挂起;裸调 StartTransaction 易遗漏错误分支,造成资源占用和数据不一致。

mongo.WithSession 是官方 Go Driver 中启动事务最稳妥的方式,直接封装了会话生命周期和错误传播逻辑,避免手动调用 CommitTransaction 或 AbortTransaction 时遗漏分支。
为什么必须用 mongo.WithSession 而不是裸写 session.StartTransaction()
裸调 session.StartTransaction() 后,如果中间操作 panic、context 超时或提前 return,事务既不会提交也不会中止,会一直挂起直到会话超时(默认 30 分钟),占用服务器资源并可能阻塞后续读写。
mongo.WithSession 内部自动处理:成功时调用 CommitTransaction;任意 error 或 panic 触发 AbortTransaction;defer 清理会话。这是唯一被驱动文档明确推荐的事务入口。
- 不支持嵌套事务 ——
mongo.WithSession内再调StartTransaction会报transaction already in progress - 传入的
context必须带超时(如context.WithTimeout(ctx, 30*time.Second)),否则事务可能无限等待锁 - 闭包函数签名必须是
func(mongo.SessionContext) error,不能省略参数名或改类型
mongo.SessionContext 和普通 context.Context 的区别在哪
mongo.SessionContext 是 context.Context 的子类型,专为事务内操作设计。所有在事务中执行的数据库方法(如 UpdateOne、InsertOne)都要求传入它,否则操作会脱离事务上下文,变成独立的非事务请求。
常见错误是把外部 context 直接传进集合操作:coll.UpdateOne(context.TODO(), ...) —— 这条语句完全不参与事务,即使包裹在 mongo.WithSession 闭包里也无效。
- 必须用闭包参数
sc(即mongo.SessionContext)调用所有 CRUD 方法 -
sc自动携带事务 ID 和快照时间戳,保证多操作读写一致性 - 不能把
sc保存成全局变量或跨 goroutine 使用,它不是线程安全的
事务失败后,mongo.WithSession 怎么处理重试
驱动不自动重试。当 mongo.WithSession 返回非 nil error(例如 write conflict、max transaction lifetime exceeded),你需要自己判断是否重试。典型策略是捕获 mongo.WriteException 并检查 ErrorLabels:
err := mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
// ... 事务体
})
if err != nil {
if writeErr, ok := err.(mongo.WriteException); ok {
for _, e := range writeErr.WriteErrors {
if e.Labels.Contains("TransientTransactionError") {
// 可重试,重新调用 mongo.WithSession
return retryWithBackoff()
}
}
}
}
- 仅
TransientTransactionError标签表示可安全重试;UnknownTransactionCommitResult需要额外查证状态 - 重试前必须新建会话(
client.StartSession()),旧会话已失效 - 不要无限制重试 —— 建议加最大次数(如 3 次)和指数退避
事务内聚合操作或 FindOneAndReplace 是否受保护
是,但有前提:所有操作必须使用同一个 mongo.SessionContext,且目标集合必须属于同一副本集(分片集群需满足事务路由约束)。聚合管道中的 $lookup、$merge 等阶段若涉及跨库/跨集合写入,必须显式指定 let 和 pipeline 上下文,否则可能报 cross-shard transactions are not allowed。
-
FindOneAndUpdate和FindOneAndReplace在事务中完全支持,原子性与主操作一致 - 聚合中含写操作(如
$out、$merge)时,目标集合必须与事务起始集合在同一数据库内 - 事务内禁止
createCollection、dropDatabase等 DDL 操作
最容易被忽略的是:事务会话对象本身不能复用 —— 每次 mongo.WithSession 调用都应基于新 client.StartSession(),哪怕只是读操作。复用旧会话可能导致快照过期或事务 ID 冲突,错误信息常表现为 InvalidSession 或静默失败。


















