Go中sql.Tx panic因未检查db.Begin()错误致tx为nil;事务提交/回滚后不可再用;须用defer+显式错误分支确保回滚,且所有SQL操作必须通过tx对象执行。

Go 用 sql.Tx 开启事务时为什么总是 panic: "sql: transaction has already been committed or rolled back"?
因为没检查 db.Begin() 返回的 error,直接用了 nil 的 *sql.Tx。Go 的事务对象不是“自动创建”的,Begin() 失败时返回 nil, err,后续调用 Commit() 或 Rollback() 就会 panic。
- 务必在
tx, err := db.Begin()后立刻判断if err != nil,不能跳过 - 事务对象
tx一旦Commit()或Rollback()过,就不可再用——哪怕只调一次,它内部状态就置为 closed - 别把
tx存到结构体字段里长期持有,它不是线程安全的,也不支持复用
如何正确写一个带回滚的 Go MySQL 事务函数?
核心是 defer + 显式错误分支:用 defer 确保出错必回滚,但必须在成功路径上手动 Commit(),否则 defer 会把成功的事务也滚掉。
func transfer(db *sql.DB, fromID, toID int, amount float64) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
<pre class="brush:php;toolbar:false;">// 扣款
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
if err != nil {
tx.Rollback()
return err
}
// 加款
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()}
- 不用
recover()也能写,但上面这个版本能兜住 panic 场景(比如中间误传了 nil 指针) - 每个
Exec()后都要检查 error 并立即Rollback(),不能攒到最后统一判 -
tx.Commit()成功后,不能再对tx做任何操作,包括再次Commit()或Rollback()
MySQL 的 autocommit 关闭后,db.Query() 还走事务吗?
不走。Go 的 database/sql 包里,所有非 tx. 开头的操作(比如直接用 db.Query()、db.Exec())都走独立连接、自动提交,和你手上的 *sql.Tx 完全无关。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 事务内所有 SQL 必须用
tx.Query()、tx.Exec(),不能混用db.Xxx() - MySQL 层面设
autocommit=0对 Go 的db对象没影响——Go 自己管理连接和事务生命周期 - 想查事务内刚改的数据?只能用同一个
tx对象去查,用db查看到的是事务前的状态(隔离级别允许的前提下)
并发执行多个事务时,MySQL 报 ERROR 1205 (HY000): Deadlock found when trying to get lock 怎么办?
死锁不是代码写错了,而是两个事务以不同顺序争抢同一组行锁。Go 层没法“避免”死锁,只能优雅重试。
- 捕获 error 是否包含
"Deadlock found"字符串(MySQL 错误码 1205),而不是靠 error 类型断言 - 重试次数建议 3–5 次,每次加小随机 delay(比如
time.Sleep(time.Millisecond * time.Duration(rand.Intn(10)))),避免雪崩 - 业务上尽量让事务按固定顺序访问表/行(例如 always update user with smaller ID first),从根源减少死锁概率
事务不是银弹,嵌套、长事务、大范围 update 都会让锁竞争更明显——先想清楚这一步是不是真需要事务,比怎么写更重要。

















