必须用begin而非transaction的两种情况:一是事务需跨多个函数传递(如logic层开启、repo层执行、logic层决定提交或回滚),二是需显式控制savepoint实现嵌套回滚点;其余业务场景均应优先使用transaction以保障panic安全与自动提交/回滚。

什么时候必须用 Begin 而不是 Transaction
绝大多数业务写操作(比如「创建订单 + 扣减库存 + 记录日志」)不该手动调 Begin,而应优先用 Transaction 函数。只有两种情况才需要 Begin:
- 事务需跨多个函数传递(例如 logic 层开启,repo 层执行,logic 层决定提交或回滚)
- 需要显式控制 savepoint 实现嵌套回滚点(
tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Begin()这类底层操作)
硬套 Begin 容易漏 Commit 或 Rollback,导致连接卡死、后续请求超时(acquire connection timeout)。
Begin 后必须显式调用 Commit 或 Rollback
手动事务没有自动兜底逻辑,出错不回滚,成功不提交,全靠你写清楚:
- 所有数据库操作必须用
tx对象,不能混用全局db(否则操作脱离事务上下文) - 必须在 defer 或错误分支中调
tx.Rollback(),否则 panic 时事务悬空 - 成功路径末尾必须调
tx.Commit(),且要检查.Error(tx.Commit().Error != nil表示提交失败)
典型写法:
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Create(&order).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Model(&user).Where("id = ?", uid).Update("balance", gorm.Expr("balance - ? ", amount)).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Commit().Error; err != nil {
tx.Rollback()
return err
}
常见错误:在 Transaction 闭包里误用 Begin
这是高发陷阱:Transaction 已经帮你管理了整个生命周期,再在里面调 Begin 会导致:
- 嵌套事务不被 GORM 原生支持,
tx.Begin()返回的新tx2不会自动绑定到外层事务 - 若只对
tx2调Commit,外层仍处于未决状态;若忘了处理,连接泄漏 - 所有操作必须严格使用传入的
tx *gorm.DB,闭包内禁止出现db.Create()
正确做法是:一个事务层级只用一种方式 —— 要么全用 Transaction,要么全用 Begin + 手动控制。
性能与安全边界:别为了“可控”放弃 Transaction
Transaction 不仅自动提交/回滚,还捕获 panic 并安全回滚,而 Begin 没有这层保障。它的开销几乎可以忽略,但可靠性差距明显:
-
Transaction是 panic-safe 的,适合大多数业务场景 -
Begin的唯一优势是灵活性(如分步提交、savepoint),但代价是责任全落到开发者肩上 - 如果只是想“先做 A,再做 B,失败就一起撤”,直接用
Transaction更稳
真正难的不是怎么写 Begin,而是判断——这个业务是否真的需要绕过 Transaction 提供的安全网。











