db.begintx(ctx, opts)是事务边界的唯一入口;必须用它开启带超时控制的事务,混用db.begin()或db操作会导致事务失效、连接泄漏与数据不一致。

db.BeginTx(ctx, opts) 是事务边界的唯一入口
Go 原生 database/sql 的 db.Begin() 不接受 context.Context,它开启的事务完全脱离超时控制。一旦上下文已过期,tx.Exec() 仍能执行、tx.Commit() 仍可成功——但此时数据库连接卡在锁等待、事务状态悬空,最终导致连接泄漏、行锁堆积、数据不一致。
正确做法只有一条:所有事务必须从 db.BeginTx(ctx, &sql.TxOptions{}) 开始,且该 ctx 必须携带明确超时(如 context.WithTimeout(ctx, 5*time.Second))。事务生命周期必须严格绑定该上下文,不能“先开事务再套 ctx”。
-
BeginTx返回的*sql.Tx本身不持有 context 引用,但底层驱动会监听ctx.Done()中断建连和语句初始化 - 若用
db.Begin(),后续所有QueryContext/ExecContext虽响应超时,但事务本身不受控 - GORM 的
db.WithContext(ctx).Transaction()底层仍调用BeginTx,但需确保传入的ctx未过期
所有 SQL 操作必须走 tx 对象,混用 db 就等于没事务
事务失效最隐蔽的原因:你在 tx 里调用了 db.Exec() 或 db.QueryRow()。这些调用会绕过事务上下文,从连接池拿新连接独立提交,而 tx.Commit() 对它们毫无影响——业务逻辑看似在事务中,实际是多个自动提交的裸 SQL。
尤其 GORM 用户要注意:模型钩子(BeforeCreate 等)里若直接调 db.Create(),就掉出事务;必须显式传入当前 tx 实例:tx.Create(&u)。
- 所有操作必须调
tx.Query()、tx.Exec()、tx.Prepare(),不能出现任何db.Xxx() - SQLite 虽无连接池,但仍需遵守此规则;PostgreSQL/MySQL 下混用会导致脏读或锁残留
- SQLC/Ent 等代码生成工具生成的函数默认接收
*sql.DB,传*sql.Tx能编译通过只是因接口兼容,不代表进了事务
defer tx.Rollback() 是兜底,不是主流程
写 defer tx.Rollback() 是为了防 panic 或提前 return 漏掉回滚,但它不能替代错误分支里的显式回滚。更危险的是把 tx.Commit() 也塞进 defer ——如果前面已 rollback,defer 还会再 commit,数据直接错乱。
Go 1.21+ 可用 tx.RollbackUnlessCommitted() 替代手动判空;老版本需自己设 tx = nil 后再 defer,否则可能对已 commit 的 tx 重复 rollback。
- 正确顺序:
tx, err := db.BeginTx(ctx, nil)→defer tx.Rollback()→ 执行 SQL →if err != nil { return err }→return tx.Commit() -
tx.Rollback()返回 error(如连接已断)通常可忽略,重点是确保它被调用 - 不要依赖
context超时来“终止正在执行的 SQL”,它只影响建连和BeginTx阻塞,不中断已发出的tx.Exec()
跨服务事务必须放弃 ACID,转向发件箱 + Saga
微服务里 tx.Commit() 成功,绝不等于其他服务的操作也成功。HTTP/gRPC 调用天然脱离 ACID,试图用 context 透传事务 ID 或“手动协调”远端行为,既不原子也不隔离。
可行路径只有两条:一是 Saga(本地事务 + 补偿),二是发件箱模式(Outbox Pattern)。两者都要求 DB 写入和事件发送在同一本地事务中完成。
- 发件箱模式:业务表插入 +
outbox_events表插入,在同一tx里完成;由独立轮询 goroutine 异步投递消息 - Saga 编排建议用
temporalio/temporal-go,而非手写状态机;补偿操作必须幂等(靠唯一约束或 Redis SETNX) - 轮询器需用
FOR UPDATE SKIP LOCKED(PostgreSQL)或乐观锁(MySQL),防止多实例重复投递
事务真正的复杂点不在怎么开启,而在边界是否清晰:SQL 执行路径有没有被中间件、ORM 钩子、日志组件意外截断或替换。每次加新层,都要检查它是否透传了当前 tx 实例。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











