深入理解行锁发生时数据分布和范围

举报
码乐 发表于 2025/02/15 08:29:47 2025/02/15
【摘要】 1简介行锁(FOR UPDATE)我们可以从这些逻辑和组织角度来理解 MySQL 的行锁机制,尤其是 FOR UPDATE 语句的作用。 2 数据分布和范围数据分布与锁范围维度MySQL 的 行锁(Row Lock) 是基于索引的,在 FOR UPDATE 语句中,只有命中了索引的行才会被锁定。若未使用索引,全表扫描时,会退化为 表锁,影响整个表的并发访问。锁的范围:索引命中 → 只锁定相...

1简介

行锁(FOR UPDATE)我们可以从这些逻辑和组织角度来理解 MySQL 的行锁机制,尤其是 FOR UPDATE 语句的作用。

2 数据分布和范围

数据分布与锁范围维度

MySQL 的 行锁(Row Lock) 是基于索引的,在 FOR UPDATE 语句中,只有命中了索引的行才会被锁定。
若未使用索引,全表扫描时,会退化为 表锁,影响整个表的并发访问。

锁的范围:

索引命中 → 只锁定相关行
索引未命中 → 可能锁整个表

时间维度(锁的持续时间)

FOR UPDATE 获取的行锁会在 事务提交(COMMIT)或回滚(ROLLBACK) 后释放。
在长事务中,锁持有时间越长,阻塞其他事务的可能性越大,容易导致死锁或性能下降。

快照读 vs 当前读:

SELECT * FROM table WHERE id=1; (快照读,不加锁)
SELECT * FROM table WHERE id=1 FOR UPDATE; (当前读,加锁)

逻辑维度(业务逻辑与锁的关系)

FOR UPDATE 适用于 防止“脏读”或“幻读”,确保同一行的数据在事务执行期间不会被其他事务修改。

典型应用场景:

库存扣减(防止超卖)
分布式锁(MySQL 充当分布式事务协调器)
乐观锁 vs 悲观锁(FOR UPDATE 属于悲观锁)

并发与架构设计:

高并发下,减少锁粒度:尽量使用 索引,避免不必要的锁范围扩大。

死锁检测与处理:
业务层避免循环依赖(A 锁 X → B 锁 Y → A 需要 Y,导致死锁)
数据库层使用 innodb_lock_wait_timeout 机制 处理超时

3 web服务 实现 MySQL 行锁(FOR UPDATE)

我们可以使用 Gin + GORM 搭建一个简单的服务,模拟一个扣减库存的业务,确保在高并发下不会出现超卖问题。

  1. 初始化数据库

     CREATE TABLE products (
         id INT PRIMARY KEY AUTO_INCREMENT,
         name VARCHAR(255) NOT NULL,
         stock INT NOT NULL
     );
    
     INSERT INTO products (name, stock) VALUES ('iPhone 15', 10);
    
  2. GORM 代码示例

      // Product 模型
      type Product struct {
          ID    uint   `gorm:"primaryKey"`
          Name  string `gorm:"type:varchar(255)"`
          Stock int
      }
    
      var db *gorm.DB
    
      func main() {
          // 连接数据库
          dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
          var err error
          db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
          if err != nil {
              log.Fatal("数据库连接失败:", err)
          }
    
          r := gin.Default()
          r.POST("/buy/:id", buyProduct) // 购买商品接口
    
          r.Run(":8080") // 运行服务
      }
    
      // 购买商品(行锁 FOR UPDATE 防止超卖)
      func buyProduct(c *gin.Context) {
          var product Product
          productID := c.Param("id")
    
          // 开启事务
          tx := db.Begin()
          if err := tx.Error; err != nil {
              c.JSON(http.StatusInternalServerError, gin.H{"error": "无法开启事务"})
              return
          }
    
          // 查询产品并加锁
          if err := tx.Raw("SELECT * FROM products WHERE id = ? FOR UPDATE", productID).Scan(&product).Error; err != nil {
              tx.Rollback()
              c.JSON(http.StatusInternalServerError, gin.H{"error": "查询产品失败"})
              return
          }
    
          // 检查库存
          if product.Stock <= 0 {
              tx.Rollback()
              c.JSON(http.StatusBadRequest, gin.H{"error": "库存不足"})
              return
          }
    
          // 扣减库存
          if err := tx.Exec("UPDATE products SET stock = stock - 1 WHERE id = ?", productID).Error; err != nil {
              tx.Rollback()
              c.JSON(http.StatusInternalServerError, gin.H{"error": "库存扣减失败"})
              return
          }
    
          // 提交事务
          if err := tx.Commit().Error; err != nil {
              c.JSON(http.StatusInternalServerError, gin.H{"error": "事务提交失败"})
              return
          }
    
          c.JSON(http.StatusOK, gin.H{"message": "购买成功", "剩余库存": product.Stock - 1})
      }
    

4 总结

FOR UPDATE 通过 索引行锁 防止高并发写入时数据不一致,避免超卖问题。

事务控制 是核心,确保 BEGIN → 查询 + FOR UPDATE → 更新 → COMMIT。

GORM 可用于构建高并发场景的 RESTful API,结合 FOR UPDATE 实现数据库级并发控制。

这样,我们就实现了一个安全的扣库存系统,防止并发超卖

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。