事务对象提交或回滚后不可复用,再次调用create/update会panic“invalid transaction”;必须通过db.begin()或db.transaction()新建事务,且所有操作须使用同一tx参数,不可混用db或跨函数隐式传递。

事务对象提交或回滚后会 panic
调用 tx.Commit() 或 tx.Rollback() 后,该 *gorm.DB 实例即进入无效状态。再对其调用 Create()、Update() 等方法,GORM 会直接 panic 报 "invalid transaction"。
这不是 bug,而是设计约束:事务实例是一次性上下文载体,底层绑定的 *sql.Tx 在提交/回滚后即关闭,无法复用。
- 错误写法:
tx.Create(...); tx.Commit(); tx.Update(...)—— 第二个操作必 panic - 正确做法:每个新事务必须重新调用
db.Begin()或走db.Transaction()函数封装 - 调试时可检查
tx.Statement.ConnPool == nil,若为true说明已失效
db.Transaction() 内部也生成一次性事务实例
db.Transaction() 是推荐的事务入口,但它内部仍是调用 db.Begin() 创建新事务,并在函数返回后自动 Commit() 或 Rollback()(仅对 panic)。它不提供“复用”能力,只是封装了生命周期管理。
- 事务函数体中所有操作都必须使用传入的
tx *gorm.DB参数,不能混用外部db - 函数内若需多次写入,应统一用该
tx,但不可在return后继续用它 - 别试图把
tx存到全局变量或缓存里——它不是线程安全的长期资源
嵌套调用时事务不会自动穿透
GORM 的事务对象不通过闭包或全局变量隐式传递。子函数里若没显式接收 tx 参数,用的仍是原始 db 或新 gorm.Open() 实例,完全脱离当前事务上下文。
- 危险模式:
func updateUser(id int) { db.Where(...).Update(...) }—— 即使在db.Transaction()里调用,也直连数据库 - 安全模式:
func updateUser(tx *gorm.DB, id int) error { tx.Where(...).Update(...) }—— 显式传参,确保复用同一事务实例 - 注意
db.Session(...)会生成新实例,必须基于tx调用,如tx.Session(...)
SavePoint 不等于可复用事务
在已有事务中调用 tx.Begin() 得到的是 SavePoint,不是新事务。它支持 RollbackTo("sp"),但依然依赖外层事务存活。一旦外层 tx 提交或回滚,所有 savepoint 都失效。
-
inner := tx.Begin()后,inner仍属tx生命周期,不能独立 commit -
inner.RollbackTo("sp")只回滚到保存点,不影响外层已执行语句 - 不要指望靠 savepoint “重置”事务对象来重复使用 —— 它只是局部回退机制
db.Transaction() 函数体内完成。











