实际开发中,多个函数可能都需要事务。一个事务调用另一个事务,就形成了嵌套。GORM 通过 SavePoint 实现嵌套事务效果。
数据库本身不支持真正的嵌套事务。外层事务和内层事务在同一个连接上,内层事务的 Commit 不会真正提交,只是结束自己的事务范围。
func OuterFunc() error {
tx := db.Begin()
defer tx.Rollback()
tx.Create(&user)
// 内层事务
tx2 := tx.Begin() // 问题:tx 已经是事务了
tx2.Create(&order)
tx2.Commit()
return tx.Commit().Error
}
这种写法在 GORM 中会有问题。
GORM 用 SavePoint 模拟嵌套事务:
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user)
return tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&order)
return nil
})
})
内层 Transaction 会创建保存点,而不是真正的事务。内层失败回滚到保存点,外层失败回滚整个事务。
手动实现嵌套:
tx := db.Begin()
tx.Create(&user)
savePoint := "sp1"
tx.SavePoint(savePoint)
if err := tx.Create(&order).Error; err != nil {
tx.RollbackTo(savePoint)
}
tx.Commit()
订单与库存
func CreateOrder(db *gorm.DB, order Order) error {
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err
}
for _, item := range order.Items {
if err := DeductStock(tx, item.ProductID, item.Quantity); err != nil {
return err
}
}
return nil
})
}
func DeductStock(tx *gorm.DB, productID uint, quantity int) error {
return tx.Transaction(func(tx2 *gorm.DB) error {
var product Product
if err := tx2.First(&product, productID).Error; err != nil {
return err
}
if product.Stock < quantity {
return errors.New("库存不足")
}
return tx2.Model(&product).Update("stock", gorm.Expr("stock - ?", quantity)).Error
})
}
DeductStock 可以独立使用,也可以在事务中被调用。
虽然 GORM 没有内置的事务传播,但可以实现类似效果:
支持外部事务
func CreateOrder(db *gorm.DB, order Order) error {
var tx *gorm.DB
if db != nil {
tx = db
} else {
tx = db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
}
if err := tx.Create(&order).Error; err != nil {
if db == nil {
tx.Rollback()
}
return err
}
if db == nil {
return tx.Commit().Error
}
return nil
}
更优雅的方式:
func WithTransaction(db *gorm.DB, fn func(tx *gorm.DB) error) error {
if isInTransaction(db) {
return fn(db)
}
return db.Transaction(fn)
}
func isInTransaction(db *gorm.DB) bool {
return db.Statement != nil && db.Statement.ConnPool != nil
}
使用:
func CreateOrder(db *gorm.DB, order Order) error {
return WithTransaction(db, func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err
}
return CreateOrderItems(tx, order.Items)
})
}
不是真正的事务
内层事务的 Commit 只是释放保存点,数据没有真正提交。
回滚范围
内层事务回滚只回到保存点,外层事务继续执行。
性能影响
每层嵌套都会创建保存点,有一定开销。
复杂业务流程
func ProcessOrder(db *gorm.DB, order Order) error {
return db.Transaction(func(tx *gorm.DB) error {
if err := CreateOrder(tx, order); err != nil {
return err
}
if err := DeductInventory(tx, order); err != nil {
return err
}
if err := CreatePayment(tx, order); err != nil {
return err
}
if err := SendNotification(tx, order); err != nil {
// 通知失败不影响订单
log.Printf("通知失败: %v", err)
}
return nil
})
}
func CreateOrder(tx *gorm.DB, order Order) error {
return tx.Transaction(func(tx2 *gorm.DB) error {
return tx2.Create(&order).Error
})
}
func DeductInventory(tx *gorm.DB, order Order) error {
return tx.Transaction(func(tx2 *gorm.DB) error {
for _, item := range order.Items {
if err := tx2.Model(&Product{}).
Where("id = ? AND stock >= ?", item.ProductID, item.Quantity).
Update("stock", gorm.Expr("stock - ?", item.Quantity)).Error; err != nil {
return err
}
}
return nil
})
}
GORM 通过 SavePoint 实现嵌套事务效果。理解其原理,合理使用事务传播模式,能让代码更灵活。