Golang 每日一题:并发更新 MySQL 数据

科技   2024-10-25 11:14   广东  

答案

import (
 "database/sql"
 "fmt"
)

// ... 数据库连接配置 ...

func DeductStock(productId int, quantity int) (bool, error) {
 // 开启数据库事务
 tx, err := db.Begin()
 if err != nil {
  return false, fmt.Errorf("开始事务失败: %v", err)
 }
 defer tx.Rollback() // 保证事务最终被回滚

 // 查询商品信息
 var currentStock int
 err = tx.QueryRow("SELECT stock FROM products WHERE id = ? FOR UPDATE", productId).Scan(&currentStock)
 if err != nil {
  return false, fmt.Errorf("查询商品信息失败: %v", err)
 }

 // 检查库存是否足够
 if currentStock < quantity {
  return false, fmt.Errorf("库存不足")
 }

 // 扣减库存
 result, err := tx.Exec("UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?", quantity, productId, quantity)
 if err != nil {
  return false, fmt.Errorf("更新库存失败: %v", err)
 }

 // 检查更新的行数
 rowsAffected, err := result.RowsAffected()
 if err != nil {
  return false, fmt.Errorf("检查更新行数失败: %v", err)
 }
 if rowsAffected == 0 {
  // 乐观锁:库存可能已被其他请求修改,扣减失败
  return false, fmt.Errorf("库存已发生变化,请重试")
 }

 // 提交事务
 err = tx.Commit()
 if err != nil {
  return false, fmt.Errorf("提交事务失败: %v", err)
 }

 return truenil
}

代码逻辑解释:

  1. 数据库事务: 使用事务保证数据一致性。
  2. 乐观锁:
  • 使用 FOR UPDATE 语句在查询商品信息时锁定该行数据,防止其他并发请求修改。
  • UPDATE 语句中添加 stock >= ? 条件,确保只有当库存足够时才进行扣减。
  • 检查更新的行数,如果为 0,说明库存已被其他请求修改,扣减失败。
  • 错误处理:  对数据库操作和逻辑判断进行错误处理,返回明确的错误信息。
  • 考点:

    • Golang 数据库操作
    • MySQL 事务处理
    • 乐观锁机制
    • 并发控制

    源自开发者
    专注于提供关于Go语言的实用教程、案例分析、最新趋势,以及云原生技术的深度解析和实践经验分享。
     最新文章