从乐观锁变成悲观锁

This commit is contained in:
2026-06-29 12:29:21 +09:00
parent ab9da7bb33
commit b930662817

View File

@@ -1120,26 +1120,31 @@ func (s *Service) createOrderWithWalletPayment(ctx context.Context, order *model
} }
actualAmount := *order.ActualPaidAmount actualAmount := *order.ActualPaidAmount
// 1. 事务外:检查钱包余额(快速失败) // 事务内:加锁读取钱包 + 余额检查 + 创建订单 + 扣款 + 创建流水 + 激活套餐
wallet, err := s.agentWalletStore.GetMainWallet(ctx, operatorShopID) // 使用 FOR UPDATE 悲观锁替代乐观锁,避免并发请求因 version 过期导致误报余额不足
if err != nil { err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err == gorm.ErrRecordNotFound { // 1. 加锁读取钱包,并发请求排队等待,不会冲突
return errors.New(errors.CodeWalletNotFound, "钱包不存在") var wallet model.AgentWallet
if err := tx.Set("gorm:query_option", "FOR UPDATE").
Where("shop_id = ? AND wallet_type = ?", operatorShopID, constants.AgentWalletTypeMain).
First(&wallet).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeWalletNotFound, "钱包不存在")
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
} }
return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
}
if wallet.Balance < actualAmount {
return errors.New(errors.CodeInsufficientBalance, "余额不足")
}
// 2. 事务内:创建订单 + 扣款 + 创建流水 + 激活套餐 // 2. 余额检查(加锁后读到的是最新余额,结果准确)
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if wallet.Balance < actualAmount {
// 2.1 创建订单 return errors.New(errors.CodeInsufficientBalance, "余额不足")
}
// 3. 创建订单
if err := tx.Create(order).Error; err != nil { if err := tx.Create(order).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建订单失败") return errors.Wrap(errors.CodeDatabaseError, err, "创建订单失败")
} }
// 2.2 创建订单明细 // 4. 创建订单明细
for _, item := range items { for _, item := range items {
item.OrderID = order.ID item.OrderID = order.ID
} }
@@ -1147,30 +1152,24 @@ func (s *Service) createOrderWithWalletPayment(ctx context.Context, order *model
return errors.Wrap(errors.CodeDatabaseError, err, "创建订单明细失败") return errors.Wrap(errors.CodeDatabaseError, err, "创建订单明细失败")
} }
// 2.3 扣减钱包余额(乐观锁 // 5. 扣减钱包余额(FOR UPDATE 已保证串行,无需 version 条件
result := tx.Model(&model.AgentWallet{}). if err := tx.Model(&wallet).Updates(map[string]any{
Where("id = ? AND balance >= ? AND version = ?", wallet.ID, actualAmount, wallet.Version). "balance": gorm.Expr("balance - ?", actualAmount),
Updates(map[string]any{ "version": gorm.Expr("version + 1"),
"balance": gorm.Expr("balance - ?", actualAmount), }).Error; err != nil {
"version": gorm.Expr("version + 1"), return errors.Wrap(errors.CodeDatabaseError, err, "扣减钱包余额失败")
})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "扣减钱包余额失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突")
} }
// 2.4 创建钱包流水 // 6. 创建钱包流水
var relatedShopID *uint var relatedShopID *uint
if order.PurchaseRole == model.PurchaseRolePurchaseForSubordinate { if order.PurchaseRole == model.PurchaseRolePurchaseForSubordinate {
relatedShopID = &buyerShopID relatedShopID = &buyerShopID
} }
if err := s.createWalletTransaction(ctx, tx, wallet, order, actualAmount, order.PurchaseRole, relatedShopID); err != nil { if err := s.createWalletTransaction(ctx, tx, &wallet, order, actualAmount, order.PurchaseRole, relatedShopID); err != nil {
return err return err
} }
// 2.5 激活套餐 // 7. 激活套餐
if err := s.activatePackage(ctx, tx, order); err != nil { if err := s.activatePackage(ctx, tx, order); err != nil {
return err return err
} }