多表联合事务回滚无特殊机制,本质是数据库级原子操作;失效主因是误用不同DB实例、跨goroutine操作、未检查错误或混用db与tx。

多表联合事务回滚本身没有特殊机制——GORM 的事务就是数据库级别的原子操作,只要所有 Create/Update/Delete 都在同一个 tx 实例上执行,失败时调用 Rollback() 就能一并回滚所有表的变更。
为什么写入多个表后回滚没生效?
常见原因是误用了不同 DB 实例或跨 goroutine 操作:
- 在
db.Transaction()回调里用了全局db而不是传入的tx,导致部分操作跑出事务边界 - 调用其他 service 函数时,内部又 new 了一个
db或复用了非事务上下文的实例 - 用了
go func() { tx.Create(...) }()并发写入,tx不是并发安全的 - 某个
Create操作没检查.Error,错误被忽略,后续仍继续执行直到Commit()
在 Gin handler 中正确开启多表事务
以「创建订单 + 扣减库存 + 记录订单商品」为例,必须全部走同一个 tx:
func CreateOrderHandler(c *gin.Context) {
var req struct {
UserID uint `json:"userId"`
GoodsID uint `json:"goodsId"`
BuyNum uint `json:"buyNum"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
<pre class="brush:php;toolbar:false;">// ✅ 正确:统一用 tx 操作所有表
err := db.Transaction(func(tx *gorm.DB) error {
// 1. 扣库存(m_goods)
if err := tx.Model(&model.Goods{}).
Where("goodsId = ? AND stock >= ?", req.GoodsID, req.BuyNum).
Update("stock", gorm.Expr("stock - ? ", req.BuyNum)).Error; err != nil {
return err // 触发回滚
}
// 2. 创建订单(m_order)
order := model.Order{UserID: req.UserID, SalePrice: 0} // 简化金额逻辑
if err := tx.Create(&order).Error; err != nil {
return err
}
// 3. 关联商品(m_order_goods)
og := model.OrderGoods{
OrderID: req.GoodsID, // 注意:实际应为 order.OrderID
GoodsID: req.GoodsID,
BuyNum: req.BuyNum,
}
if err := tx.Create(&og).Error; err != nil {
return err
}
return nil // ✅ 成功则自动 Commit
})
if err != nil {
c.JSON(500, gin.H{"error": "事务失败:" + err.Error()})
return
}
c.JSON(200, gin.H{"message": "success"})}
嵌套事务不能实现“局部回滚”
GORM 的 Begin() 嵌套只是建 savepoint,不是独立事务:
-
inner.Rollback()只回滚到 savepoint,外层已执行的语句仍在 - 但
outer.Rollback()会清掉所有 savepoint,整个事务失效 - 别指望靠它实现“订单创建成功、库存扣减失败也保留订单”——这违反 ACID,GORM 不提供跨事务的数据可见性控制
性能与结构陷阱
默认 GORM 对每个 Create/Update/Delete 都包一层事务,开销不小:
- 若你确定单条操作彼此无关(比如写日志、发通知),初始化时设
SkipDefaultTransaction: true,性能可提 30%+ - 但多表联合场景下,必须显式用
Transaction()或Begin(),否则各表各自提交,无法保证一致性 - 分层架构中,
repository层必须接收*gorm.DB参数(而非全局db),才能把事务上下文透传下去
真正容易被忽略的是:事务回滚只对数据库生效,不逆转内存状态、不重置缓存、不撤回 HTTP 响应。一旦 Commit() 成功,哪怕后续代码 panic,数据也已落库。


















