必须全程使用tx实例操作,禁止混用db;推荐用db.Transaction()闭包自动管理事务,它自动回滚panic和error、自动提交成功路径,避免连接泄漏和原子性破坏。

直接用 db.Begin() 开启事务,但别只写这一行就完事——它返回的 *gorm.DB 实例必须全程传递、不能混用原始 db,否则事务根本不起作用。
手动开启事务:必须用 tx 替代 db 执行所有操作
常见错误是开了事务却还在用原始 db 对象做增删改查,比如:
tx := db.Begin() db.Create(&user) // ❌ 错!这不在事务里 tx.Create(&user) // ✅ 对!必须用 tx
原因很简单:tx 是带事务上下文的新 *gorm.DB 实例,而 db 是无事务的全局连接池句柄。GORM 不会自动把 db 的调用“塞进”当前事务。
- 所有 CRUD 操作(
Create、Save、Delete、Model().Update等)都必须调用tx上的方法 - 关联操作(如
tx.Preload().First())也必须走tx,否则预加载可能读到未提交数据或脏读 - 事务内执行原生 SQL(
tx.Raw().Exec())同样要绑定tx
db.Transaction():更安全的函数式封装,自动处理回滚
相比手动 Begin/Commit/Rollback,db.Transaction() 是推荐方式,它把事务逻辑收进一个闭包,出错自动回滚:
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&userA).Error; err != nil {
return err // 自动触发 rollback
}
if err := tx.Create(&userB).Error; err != nil {
return err // 自动触发 rollback
}
return nil // 成功则自动 commit
})
这个模式天然规避了「忘记 Rollback」或「Commit 失败后没补 Rollback」的问题。
- 闭包内任何地方
return err,GORM 都会调用tx.Rollback() - 闭包正常返回
nil,GORM 自动调用tx.Commit() - 不支持嵌套调用(即闭包里再调
tx.Transaction()),会 panic
提交失败时必须显式回滚,否则连接可能卡住
手动管理事务时,tx.Commit().Error 报错不代表事务已回滚——GORM 不会帮你兜底:
tx := db.Begin()
tx.Create(&user)
if err := tx.Commit().Error; err != nil {
tx.Rollback() // ⚠️ 必须写!否则该连接可能留在“pending commit”状态
return err
}
MySQL 和 PostgreSQL 都要求显式回滚未完成事务,否则连接会被占用,长时间积累导致连接池耗尽。
- 即使
Commit()返回 error,也要立即调tx.Rollback() - 如果
Rollback()本身也出错(极少见),通常说明底层连接已断开,此时应记录日志并放弃该事务 - 别依赖 defer 写
tx.Rollback(),它会在函数退出时执行,但你可能在中间已成功Commit(),这时再 Rollback 会报 “sql: transaction has already been committed or rolled back”
真正容易被忽略的是:事务对象 tx 不是线程安全的,不能在 goroutine 间共享;同时,它不继承原始 db 的配置(如 Logger、NowFunc),如有定制需求需手动复制。











