db.begintx(ctx, opts)支持传入context控制超时和txoptions设置隔离级别,而db.begin()无context、无超时、仅等价于begintx(context.background(), nil);事务内所有操作必须用tx对象,混用db会导致静默数据泄露。

db.Begin() 和 db.BeginTx(ctx, opts) 的区别必须分清
不用 context.Context 的事务没有超时控制能力,一旦某条语句卡在锁等待或网络抖动里,整个 goroutine 就会挂起,连接池可能被耗尽。Go 1.21+ 推荐统一用 db.BeginTx(ctx, opts),哪怕只是传 context.Background(),也为后续加 timeout 留出接口。
db.Begin() 是历史兼容写法,等价于 db.BeginTx(context.Background(), nil);而 db.BeginTx() 才支持传入 &sql.TxOptions{Isolation: sql.LevelSerializable} 或自定义隔离级别。
- MySQL 默认隔离级别是
REPEATABLE READ,但 Go 层不显式设TxOptions就不会发SET TRANSACTION ISOLATION LEVEL语句 - SQLite 的
serializable实际退化为排他锁,别指望它真做快照隔离 - PostgreSQL 对
READ COMMITTED支持最好,SERIALIZABLE开销大,线上慎用
事务内所有 SQL 必须走 tx 对象,混用 db 是静默数据泄露
这是最常踩的坑:在 tx 里不小心调了 db.Query() 或 db.Exec(),那条语句根本不在事务上下文中——它从连接池拿了新连接,执行完立刻提交,tx.Commit() 完全感知不到。
现象是:tx.Commit() 返回 nil,但查库发现部分数据已写入、部分没写,状态撕裂。尤其容易发生在封装了 DAO 方法后,忘了把 *sql.Tx 作为参数透传进去。
- 所有操作必须用
tx.Query()、tx.Exec()、tx.Prepare(),不能出现任何db.Xxx() -
tx不是线程安全的,别跨 goroutine 复用(比如在go func() { tx.Exec(...) }()里用) - 如果 DAO 层要复用,函数签名得是
func updateUser(tx *sql.Tx, id int, name string) error,而不是func updateUser(db *sql.DB, ...)
rollback 必须 defer,且要在 BeginTx 后立刻注册
手动写 if err != nil { tx.Rollback() } 极易遗漏:panic 时跳过、提前 return、嵌套 error 分支没覆盖、甚至忘记写。正确姿势是 defer tx.Rollback() 紧跟 db.BeginTx() 后,再在 tx.Commit() 成功后把 tx 置为 nil 来阻止 rollback 执行。
Go 1.21+ 提供了 tx.RollbackUnlessCommitted(),更安全,但老版本仍需自己判空。注意:tx.Rollback() 返回 error 通常说明连接已断,此时日志也写不了,直接忽略即可。
- 别在
defer里包一层recover()再 rollback ——tx本身不 panic,panic 是你代码抛的,recover 只是补救,不是正路 - 如果事务里调了外部 HTTP 服务,超时或失败不能只靠 defer rollback,得主动记录状态,否则服务重启后无法续跑
- 多个数据库操作嵌套时,每个子函数不该自行 rollback,而应让顶层统一控制
GORM 场景下事务传递比原生 sql 更容易出错
GORM 的 Session、Transaction、WithContext 混用时,很容易误以为“用了 context 就进了事务”,其实 GORM 的事务对象和 context 是两套机制。比如 db.WithContext(ctx).Create(&u) 不会自动绑定到当前 tx,除非显式用 tx.Create()。
常见错误是:DAO 层用 db.Transaction() 包了一段逻辑,但里面又调了另一个封装好的 userRepo.Update(),而这个方法内部又 new 了一个 db 实例,结果事务完全没生效。
- GORM v2 推荐用
db.Session(&gorm.Session{NewDB: true}).Transaction(...)配合显式传参,避免隐式上下文污染 - 若用
db.WithContext(ctx),必须确保 ctx 里存了*gorm.DB实例(通过context.WithValue),且下游都用ctx.Value(...).(*gorm.DB)取 - 跨微服务调用时,GORM 事务无法传播,Saga 补偿必须独立建模,别指望 ORM 自动帮你回滚远端 DB
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











