Transaction是业务代码的安全默认项,因其自动处理提交/回滚、panic安全且避免连接泄漏;必须用传入的tx操作,禁用全局db,不支持嵌套事务。

绝大多数业务写操作必须用 Transaction,而不是 Begin;后者只适合跨函数传事务或手动管理 savepoint 的底层场景。
为什么 Transaction 是业务代码的安全默认项
它把事务生命周期完全封装在闭包里:进闭包即开启事务,出闭包时——无论正常返回还是 panic——都会自动 Commit 或 Rollback。你不用写 defer tx.Rollback(),也不用担心忘记 tx.Commit() 导致连接卡死。
- 返回
nil→ 自动提交 - 返回非
nilerror → 自动回滚 - 闭包内发生 panic → 被捕获并触发回滚(
recover内置) - 所有 DB 操作必须用传入的
tx *gorm.DB,不能混用全局db
Begin 什么时候真得用
Begin 返回裸事务句柄,不带自动收尾逻辑,仅适用于两种明确场景:
- 需要把事务对象从 handler 层传到 service 层再传到 repo 层(跨函数边界共享同一事务上下文)
- 要手动设置 savepoint 并做局部回滚(例如:A 步失败不影响 B 步,但 B 步失败要回滚到 A 后)
硬套 Begin 写业务逻辑,90% 以上会漏 Commit 或 Rollback,轻则连接池耗尽、请求超时,重则数据部分落库、状态不一致。
立即学习“go语言免费学习笔记(深入)”;
常见错误:在 Transaction 闭包里误用全局 db
这是最隐蔽也最危险的坑。比如:
err := db.Transaction(func(tx *gorm.DB) error {
tx.Create(&Order{}) // ✅ 正确:走事务上下文
db.Create(&Log{}) // ❌ 错误:走新连接,立刻落库!破坏原子性
return nil
})
只要用了全局 db,哪怕只有一行,整个事务就失效了。所有操作必须统一用参数传入的 tx 实例。
嵌套事务不被支持,别试图在 Transaction 里再调 Transaction
GORM 的 Transaction 不支持嵌套。如果你看到类似“先扣库存再发消息,发消息失败要回滚库存”的需求,这不是嵌套事务问题,而是业务拆分问题:
- 扣库存和发消息应属同一事务边界(要么都成功,要么都失败)
- 若发消息依赖外部服务,应改用补偿机制(如本地记录消息任务表 + 定时重试),而非强行塞进事务
- 真要用 savepoint,必须用
Begin+tx.SavePoint()+tx.RollbackTo()手动控制
事务不是万能胶,它的边界由业务一致性定义,而不是代码缩进层级。


















