事务处理
2026/1/15大约 5 分钟GORM事务Transaction
事务处理
一、事务基础
1.1 什么是事务
事务是一组数据库操作的集合,要么全部成功,要么全部失败。
ACID 特性:
- 原子性(Atomicity):事务中的操作要么全部完成,要么全部不完成
- 一致性(Consistency):事务前后数据的完整性保持一致
- 隔离性(Isolation):多个事务并发执行时互不干扰
- 持久性(Durability):事务提交后,数据永久保存
1.2 为什么需要事务
// 转账操作需要事务保证
// 1. A 账户扣款
// 2. B 账户加款
// 如果第2步失败,第1步需要回滚二、手动事务
2.1 基本用法
// 开启事务
tx := db.Begin()
// 执行操作
if err := tx.Create(&user).Error; err != nil {
// 回滚
tx.Rollback()
return err
}
if err := tx.Create(&profile).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
tx.Commit()2.2 完整示例
func (s *UserService) CreateUserWithProfile(user *User, profile *Profile) error {
// 开启事务
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 创建用户
if err := tx.Create(user).Error; err != nil {
tx.Rollback()
return err
}
// 设置 Profile 的 UserID
profile.UserID = user.ID
// 创建 Profile
if err := tx.Create(profile).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
return tx.Commit().Error
}三、自动事务
3.1 Transaction 方法
err := db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行操作
if err := tx.Create(&user).Error; err != nil {
// 返回错误会自动回滚
return err
}
if err := tx.Create(&profile).Error; err != nil {
return err
}
// 返回 nil 会自动提交
return nil
})3.2 嵌套事务
db.Transaction(func(tx *gorm.DB) error {
// 外层事务
tx.Create(&user)
// 嵌套事务
return tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&profile)
return nil
})
})3.3 SavePoint
tx := db.Begin()
tx.Create(&user1)
// 创建保存点
tx.SavePoint("sp1")
tx.Create(&user2)
// 回滚到保存点
tx.RollbackTo("sp1")
tx.Commit()四、实战示例
4.1 转账操作
type Account struct {
ID uint
UserID uint
Balance decimal.Decimal
}
func (s *AccountService) Transfer(fromID, toID uint, amount decimal.Decimal) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 查询转出账户(加锁)
var fromAccount Account
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
First(&fromAccount, fromID).Error; err != nil {
return err
}
// 检查余额
if fromAccount.Balance.LessThan(amount) {
return errors.New("余额不足")
}
// 扣款
if err := tx.Model(&fromAccount).
Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
return err
}
// 查询转入账户(加锁)
var toAccount Account
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
First(&toAccount, toID).Error; err != nil {
return err
}
// 加款
if err := tx.Model(&toAccount).
Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
return err
}
// 记录转账日志
log := TransferLog{
FromAccountID: fromID,
ToAccountID: toID,
Amount: amount,
}
if err := tx.Create(&log).Error; err != nil {
return err
}
return nil
})
}4.2 订单创建
type Order struct {
ID uint
UserID uint
TotalPrice decimal.Decimal
Status int
Items []OrderItem
}
type OrderItem struct {
ID uint
OrderID uint
ProductID uint
Quantity int
Price decimal.Decimal
}
func (s *OrderService) CreateOrder(userID uint, items []OrderItem) (*Order, error) {
var order Order
err := s.db.Transaction(func(tx *gorm.DB) error {
// 计算总价
var totalPrice decimal.Decimal
for _, item := range items {
// 查询商品(加锁)
var product Product
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
First(&product, item.ProductID).Error; err != nil {
return err
}
// 检查库存
if product.Stock < item.Quantity {
return fmt.Errorf("商品 %s 库存不足", product.Name)
}
// 扣减库存
if err := tx.Model(&product).
Update("stock", gorm.Expr("stock - ?", item.Quantity)).Error; err != nil {
return err
}
// 计算价格
item.Price = product.Price
totalPrice = totalPrice.Add(product.Price.Mul(decimal.NewFromInt(int64(item.Quantity))))
}
// 创建订单
order = Order{
UserID: userID,
TotalPrice: totalPrice,
Status: 1,
}
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
}
order.Items = items
return nil
})
return &order, err
}4.3 批量操作
func (s *UserService) BatchCreate(users []User) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 批量创建用户
if err := tx.CreateInBatches(users, 100).Error; err != nil {
return err
}
// 为每个用户创建默认配置
var configs []UserConfig
for _, user := range users {
configs = append(configs, UserConfig{
UserID: user.ID,
Theme: "default",
})
}
if err := tx.CreateInBatches(configs, 100).Error; err != nil {
return err
}
return nil
})
}五、事务隔离级别
5.1 设置隔离级别
import "gorm.io/gorm/clause"
// READ UNCOMMITTED
tx := db.Begin(&sql.TxOptions{Isolation: sql.LevelReadUncommitted})
// READ COMMITTED
tx := db.Begin(&sql.TxOptions{Isolation: sql.LevelReadCommitted})
// REPEATABLE READ(MySQL 默认)
tx := db.Begin(&sql.TxOptions{Isolation: sql.LevelRepeatableRead})
// SERIALIZABLE
tx := db.Begin(&sql.TxOptions{Isolation: sql.LevelSerializable})5.2 隔离级别对比
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
六、锁机制
6.1 悲观锁
import "gorm.io/gorm/clause"
// FOR UPDATE(排他锁)
var user User
db.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, 1)
// FOR SHARE(共享锁)
db.Clauses(clause.Locking{Strength: "SHARE"}).First(&user, 1)
// NOWAIT(不等待)
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).First(&user, 1)
// SKIP LOCKED(跳过锁定行)
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "SKIP LOCKED",
}).Find(&users)6.2 乐观锁
type Product struct {
ID uint
Name string
Price decimal.Decimal
Version int `gorm:"default:0"` // 版本号
}
// 更新时检查版本号
func (s *ProductService) UpdatePrice(id uint, newPrice decimal.Decimal) error {
return s.db.Transaction(func(tx *gorm.DB) error {
var product Product
if err := tx.First(&product, id).Error; err != nil {
return err
}
// 更新价格并增加版本号
result := tx.Model(&Product{}).
Where("id = ? AND version = ?", id, product.Version).
Updates(map[string]interface{}{
"price": newPrice,
"version": product.Version + 1,
})
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("数据已被修改,请重试")
}
return nil
})
}七、事务钩子
7.1 AfterCommit
func (u *User) AfterCommit(tx *gorm.DB) error {
// 事务提交后执行
log.Printf("用户 %s 事务已提交", u.Name)
return nil
}7.2 AfterRollback
func (u *User) AfterRollback(tx *gorm.DB) error {
// 事务回滚后执行
log.Printf("用户 %s 事务已回滚", u.Name)
return nil
}八、禁用事务
8.1 全局禁用
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
})8.2 单次禁用
db.Session(&gorm.Session{SkipDefaultTransaction: true}).Create(&user)九、性能优化
9.1 减少事务范围
// 不好:事务范围太大
db.Transaction(func(tx *gorm.DB) error {
// 大量查询操作
tx.Find(&users)
// 业务逻辑处理
processUsers(users)
// 更新操作
tx.Updates(&users)
return nil
})
// 好:只在必要时使用事务
users := []User{}
db.Find(&users)
processUsers(users)
db.Transaction(func(tx *gorm.DB) error {
return tx.Updates(&users).Error
})9.2 批量操作优化
// 使用批量操作代替循环
db.Transaction(func(tx *gorm.DB) error {
// 不好
for _, user := range users {
tx.Create(&user)
}
// 好
return tx.CreateInBatches(users, 100).Error
})十、常见问题
10.1 事务未提交
// 错误:忘记提交
tx := db.Begin()
tx.Create(&user)
// 缺少 tx.Commit()
// 正确
tx := db.Begin()
tx.Create(&user)
tx.Commit()10.2 死锁问题
// 避免死锁:按相同顺序获取锁
db.Transaction(func(tx *gorm.DB) error {
// 总是按 ID 升序锁定
ids := []uint{id1, id2}
sort.Slice(ids, func(i, j int) bool {
return ids[i] < ids[j]
})
for _, id := range ids {
var account Account
tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, id)
// 处理账户
}
return nil
})10.3 长事务
// 避免长事务
// 不好:事务中包含耗时操作
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&order)
// 发送邮件(耗时操作)
sendEmail(order)
return nil
})
// 好:耗时操作放在事务外
var order Order
db.Transaction(func(tx *gorm.DB) error {
return tx.Create(&order).Error
})
sendEmail(order)