事务是数据库操作的原子单位。要么全部成功,要么全部失败,没有中间状态。银行转账是经典例子:A 账户扣款,B 账户入账,两步操作必须同时成功或同时失败。
没有事务时:
func Transfer(fromID, toID uint, amount float64) error {
db.Model(&Account{}).Where("id = ?", fromID).Update("balance", gorm.Expr("balance - ?", amount))
db.Model(&Account{}).Where("id = ?", toID).Update("balance", gorm.Expr("balance + ?", amount))
return nil
}
如果第一步成功、第二步失败,钱就凭空消失了。事务保证这种情况不会发生。
最基本的事务用法:
tx := db.Begin()
err := tx.Create(&User{Name: "张三"}).Error
if err != nil {
tx.Rollback()
return err
}
err = tx.Create(&Order{UserID: user.ID, Amount: 100}).Error
if err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
Begin() 开始事务,Commit() 提交,Rollback() 回滚。
更简洁的写法,自动处理提交和回滚:
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&User{Name: "张三"}).Error; err != nil {
return err
}
if err := tx.Create(&Order{Amount: 100}).Error; err != nil {
return err
}
return nil
})
if err != nil {
fmt.Println("事务失败:", err)
}
闭包返回 nil 自动提交,返回错误自动回滚。
GORM 支持设置事务隔离级别:
db.Begin(&sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
常用隔离级别:
| 级别 | 说明 |
|---|---|
| LevelDefault | 数据库默认 |
| LevelReadUncommitted | 读未提交 |
| LevelReadCommitted | 读已提交 |
| LevelRepeatableRead | 可重复读 |
| LevelSerializable | 串行化 |
MySQL 默认是 REPEATABLE READ,PostgreSQL 默认是 READ COMMITTED。
db.Begin(&sql.TxOptions{
ReadOnly: true,
})
只读事务只能执行查询操作,尝试写入会报错。适合报表统计等场景。
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&order).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
用 defer + recover 捕获 panic,确保事务回滚。
银行转账
func Transfer(db *gorm.DB, fromID, toID uint, amount float64) error {
return db.Transaction(func(tx *gorm.DB) error {
var fromAccount, toAccount Account
if err := tx.First(&fromAccount, fromID).Error; err != nil {
return err
}
if err := tx.First(&toAccount, toID).Error; err != nil {
return err
}
if fromAccount.Balance < amount {
return errors.New("余额不足")
}
if err := tx.Model(&fromAccount).Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
return err
}
if err := tx.Model(&toAccount).Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
return err
}
transfer := Transfer{
FromID: fromID,
ToID: toID,
Amount: amount,
}
return tx.Create(&transfer).Error
})
}
订单创建
func CreateOrder(db *gorm.DB, order Order, items []OrderItem) error {
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err
}
for i := range items {
items[i].OrderID = order.ID
}
if err := tx.Create(&items).Error; err != nil {
return err
}
for _, item := range items {
var product Product
if err := tx.First(&product, item.ProductID).Error; err != nil {
return err
}
if product.Stock < item.Quantity {
return errors.New("库存不足")
}
if err := tx.Model(&product).Update("stock", gorm.Expr("stock - ?", item.Quantity)).Error; err != nil {
return err
}
}
return nil
})
}
用户注册
func Register(db *gorm.DB, user User, profile Profile) error {
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err
}
profile.UserID = user.ID
if err := tx.Create(&profile).Error; err != nil {
return err
}
return nil
})
}
GORM 没有像 Spring 那样的事务传播机制。如果需要在事务中调用其他函数:
func CreateOrderWithItems(tx *gorm.DB, order Order, items []OrderItem) error {
if tx == nil {
return db.Transaction(func(t *gorm.DB) error {
return CreateOrderWithItems(t, order, items)
})
}
if err := tx.Create(&order).Error; err != nil {
return err
}
for i := range items {
items[i].OrderID = order.ID
}
return tx.Create(&items).Error
}
忘记提交或回滚
tx := db.Begin()
tx.Create(&user)
// 忘记 Commit 或 Rollback
连接会一直被占用,导致连接泄漏。务必确保每个 Begin 都有对应的 Commit 或 Rollback。
在事务外操作
tx := db.Begin()
tx.Create(&user)
db.Create(&order) // 错误:用 db 而不是 tx
tx.Commit()
order 不在事务中,事务回滚时它不会回滚。
事务超时
长时间事务会占用资源,设置超时:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 事务操作
})
事务是数据一致性的保障。掌握手动事务和闭包事务两种方式,理解隔离级别的影响,注意错误处理和资源释放。