从整体角度理解服务中的行锁

举报
码乐 发表于 2025/02/16 11:42:39 2025/02/16
【摘要】 1 简介MySQL 的行锁(FOR UPDATE)。 在理解 MySQL 的 行锁(FOR UPDATE) 时,我们可以从以下整体性、层次性、动态性和关联性方面进行分析。 2 整体性(Holism):行锁在数据库系统中的作用在数据库事务管理中,数据一致性 是核心目标,而锁机制是实现一致性的重要手段。行锁(Row Lock) 是 MySQL InnoDB 存储引擎提供的细粒度锁,通过索引加锁...

1 简介

MySQL 的行锁(FOR UPDATE)。 在理解 MySQL 的 行锁(FOR UPDATE) 时,我们可以从以下整体性、层次性、动态性和关联性方面进行分析。

2 整体性(Holism):行锁在数据库系统中的作用

在数据库事务管理中,数据一致性 是核心目标,而锁机制是实现一致性的重要手段。

行锁(Row Lock) 是 MySQL InnoDB 存储引擎提供的细粒度锁,通过索引加锁来减少并发冲突。
FOR UPDATE 语句用于在事务中锁定查询到的行,防止其他事务并发修改。
作用:
确保数据一致性,防止丢失更新问题(Lost Update)
避免幻读,适用于可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)
保证操作的原子性,确保多个事务不会同时修改相同数据

3 层次性(Hierarchy):MySQL 锁的层次

在 MySQL 事务管理中,锁具有不同的粒度,从粗到细包括:

表锁(Table Lock):锁住整张表,影响所有行,适用于 MyISAM(影响整个业务)。
页锁(Page Lock):锁定某一页的数据,影响一部分行,适用于 B+ 树索引。
行锁(Row Lock):锁住特定行,FOR UPDATE 主要作用于行锁,减少并发冲突,提高吞吐量。
行锁的具体表现:

    SELECT ... FOR UPDATE:锁定查询到的行,防止其他事务修改。
    SELECT ... LOCK IN SHARE MODE:共享锁,允许其他事务读取,但不允许修改。

隐式锁 vs 显式锁:
显式锁:使用 FOR UPDATE 或 LOCK IN SHARE MODE
隐式锁:在 UPDATE 或 DELETE 语句执行时,自动加锁

4 动态性(Dynamism):事务执行中的锁行为

锁的行为随着事务执行而动态变化:

事务开启(BEGIN)
查询数据并加锁(SELECT … FOR UPDATE)
执行更新操作
事务提交(COMMIT,释放行锁)或事务回滚(ROLLBACK,释放行锁)
示例:

    BEGIN;
    SELECT * FROM products WHERE id = 1 FOR UPDATE;
    UPDATE products SET stock = stock - 1 WHERE id = 1;
    COMMIT;

注意事项:

事务未提交时,行锁不会释放
长时间持有锁可能导致死锁或阻塞其他事务
加锁必须基于索引,否则可能升级为表锁

5 关联性(Interrelation):FOR UPDATE 在业务中的应用

在高并发业务场景下,行锁常用于防止数据并发修改导致不一致,典型应用包括:

库存扣减(防止超卖)
银行转账(确保余额正确性)
任务分配(防止任务重复执行)
分布式锁(结合数据库实现分布式事务)


使用GORM 实现 FOR UPDATE(库存扣减示例)
下面我们实现一个GORM 的服务,用 FOR UPDATE 机制确保并发扣减库存的正确性。

    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);
  • 实现的GORM 代码

          // Product 模型
          type Product struct {
              ID    uint   `gorm:"primaryKey"`
              Name  string `gorm:"type:varchar(255)"`
              Stock int
          }
    
          var db *gorm.DB
    
          func main() {
              // 连接 MySQL
              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})
          }
    

6 总结

多维度 FOR UPDATE 的体现

整体性 确保数据一致性,防止超卖
层次性 只锁定特定行,避免影响其他行
动态性 锁的释放和持有受事务控制
关联性 适用于库存扣减、银行转账、任务调度等场景

最佳实践:

基于索引加锁,避免表锁
减少事务持有时间,防止长时间锁定
考虑 MySQL 隔离级别,防止幻读
使用超时机制(innodb_lock_wait_timeout)避免死锁

这种方式的整体思维,帮助理解 MySQL 行锁(FOR UPDATE) 的本质及应用,并通过GORM 提供了一个可落地的实现方案。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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