gorm事务需手动开启和结束,每个操作默认自动提交;必须用db.begin()获取事务实例并全程使用,嵌套事务用session(&gorm.session{newtx: true}),关联操作须传入事务对象,超时和panic需主动处理。

事务必须手动开启,GORM不会自动包裹
默认情况下,GORM 的每个 Create、Save、Delete 等操作都是独立的自动提交语句。如果你没显式调用 Begin,就不存在事务上下文——哪怕代码写在同一个函数里,也绝不具备原子性。
常见错误现象:panic: invalid transaction 或部分写入成功、部分失败却无法回滚。根本原因是误以为“连着调几个 Save 就天然在事务里”。
- 必须先用
db.Begin()获取一个带事务的*gorm.DB实例 - 后续所有操作(查询、写入、关联保存)都得基于这个新实例,不能混用原始
db - 事务结束后必须显式调用
Commit()或Rollback(),否则连接可能被长期占用
正确嵌套事务:使用 Session(&session.Options{NewTx: true})
当已有事务上下文(比如外层已 Begin),又想在子逻辑中“另起一个可独立提交/回滚”的事务时,不能再次调用 Begin(会报错或行为未定义)。此时要用会话隔离:
-
db.Session(&gorm.Session{NewTx: true})会强制开启新事务,且与父事务无关 - 适用于“主流程失败要整体回滚,但日志记录这类旁路操作需单独提交”的场景
- 注意:MySQL 不支持真正的嵌套事务,GORM 是通过保存点(
SAVEPOINT)模拟的,PostgreSQL 则原生支持
示例:
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 子事务:即使这里 panic,不影响 tx 的状态判断
logDB := tx.Session(&gorm.Session{NewTx: true})
logDB.Create(&Log{Msg: "started"})
logDB.Commit() // 单独提交日志
事务内关联数据保存要传入事务对象
用 Preload 或 Select 加载关联后修改并保存,如果没把事务实例传进去,GORM 会退回到默认无事务的 db 上执行,导致部分数据落库、部分丢失。
- 关联结构体字段更新后,调用
Save时必须确保该*gorm.DB是事务实例 - 批量插入关联数据(如
Append)同理,务必检查接收方是否是tx而非原始db - 用
tx.First(&user, 1)查出数据后,再tx.Model(&user).Association("Orders").Append(orders)才安全
事务超时与连接泄漏风险
GORM 本身不管理事务生命周期时长,全靠开发者控制。长时间未 Commit 或 Rollback 的事务会持续占用数据库连接,尤其在连接池小、QPS 高时极易触发 too many connections。
- 建议在事务入口加
context.WithTimeout,并在defer中统一处理回滚 - 避免在事务内做 HTTP 请求、文件读写等不可控耗时操作
- PostgreSQL 对空闲事务有
idle_in_transaction_session_timeout限制,默认关;MySQL 则依赖wait_timeout,但只对空闲连接生效,对活跃事务无效
最易被忽略的是 panic 后的清理——务必用 defer + recover 组合确保回滚,否则生产环境可能卡住整个连接池。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











