从整体角度理解nextkey锁

举报
码乐 发表于 2025/02/19 09:58:00 2025/02/19
【摘要】 1 简介整体方式理解 MySQL 的 Next-Key 锁,在理解 MySQL Next-Key 锁 及其与 FOR UPDATE 的对比时,可以从整体性、层次性、动态性和关联性这些角度进行分析。 2 . 整体性(Holism):Next-Key 锁在数据库系统中的作用在 MySQL InnoDB 存储引擎中,锁机制的作用是 保证事务的隔离性和数据一致性。Next-Key 锁(Next-K...

1 简介

整体方式理解 MySQL 的 Next-Key 锁,在理解 MySQL Next-Key 锁 及其与 FOR UPDATE 的对比时,可以从整体性、层次性、动态性和关联性这些角度进行分析。

2 . 整体性(Holism):

Next-Key 锁在数据库系统中的作用
在 MySQL InnoDB 存储引擎中,锁机制的作用是 保证事务的隔离性和数据一致性。

Next-Key 锁(Next-Key Lock) 是 行锁(Record Lock)+ 间隙锁(Gap Lock) 的组合,主要用于 防止幻读,确保 REPEATABLE READ 隔离级别的事务一致性。

行锁(FOR UPDATE) vs. Next-Key 锁

		锁类型 			作用  				适用场景    		并发影响
  	行锁(FOR UPDATE)  仅锁定查询到的行   	单行更新、扣库存    高并发友好(不影响其他行)
  Next-Key 锁  		锁定范围+间隙,防止插入   范围查询(WHERE col > X) 低并发友好度(防止幻读,但可能阻塞插入)

3 . 层次性(Hierarchy):锁的范围

MySQL InnoDB 采用 索引级别的锁,当查询涉及范围扫描时,会触发 Next-Key 锁:

  行锁(Record Lock):仅锁住某一行,如 SELECT * FROM users WHERE id = 5 FOR UPDATE;
  间隙锁(Gap Lock):锁住查询范围的“空隙”,如 WHERE age > 30
  Next-Key 锁(Record Lock + Gap Lock):
  锁住 查询范围内的行 和 行之间的间隙

阻止新的数据插入,防止幻读
仅在 REPEATABLE READ 及更严格的隔离级别下生效
示例 假设 users 表有以下数据:

    +----+------+-----+
    | id | name | age |
    +----+------+-----+
    | 1  | Alice | 28 |
    | 2  | Bob   | 35 |
    | 3  | Carol | 40 |
    +----+------+-----+

事务 1(开启 Next-Key 锁)

    BEGIN;
    SELECT * FROM users WHERE age > 30 FOR UPDATE;

锁住 Bob(35) 和 Carol(40)
也锁住 (30, 35] 和 (35, 40] 之间的间隙
其他事务无法插入 age=32 的用户

事务 2(尝试插入)

        INSERT INTO users (name, age) VALUES ('Dave', 32); -- 阻塞或失败

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

Next-Key 锁的行为随事务执行而变化:

事务开启 (BEGIN)
查询带锁 (SELECT … FOR UPDATE 触发 Next-Key 锁)

执行数据修改
事务提交 (COMMIT 释放 Next-Key 锁)

Next-Key 锁 vs. 行锁

    锁类型 			范围 				 阻止写  		适用场景
    行锁(FOR UPDATE)  仅锁定查询到的行    否   	单行更新
    Next-Key 锁  	锁住行 + 间隙    		是  		 防止幻读(范围查询)

5. 关联性(Interrelation):Next-Key 锁的业务应用

Next-Key 锁适用于防止幻读的场景,如:

防止用户在同一事务中插入新的订单,导致重复计算
金融系统防止某个时间范围内新增交易
多表关联查询,确保一致性
但 Next-Key 锁可能降低并发,适用场景有限!

使用web服务 实现 Next-Key 锁
我们 设计一个查询年龄范围用户的锁定系统:

使用 FOR UPDATE 仅锁定行
使用 Next-Key 锁防止新的用户插入

  1. 初始化数据库

     CREATE TABLE users (
         id INT PRIMARY KEY AUTO_INCREMENT,
         name VARCHAR(255) NOT NULL,
         age INT NOT NULL
     );
    
     INSERT INTO users (name, age) VALUES ('Alice', 28), ('Bob', 35), ('Carol', 40);
    
  2. 服务代码

         // User 模型
         type User struct {
             ID   uint   `gorm:"primaryKey"`
             Name string `gorm:"type:varchar(255)"`
             Age  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("/lock_users/:age", lockUsersByAge) // 触发 Next-Key 锁
             r.POST("/add_user", addUser) // 新增用户
    
             r.Run(":8080") // 启动服务
         }
    
         // 1. 使用 Next-Key Lock 查询用户
         func lockUsersByAge(c *gin.Context) {
             var users []User
             age := c.Param("age")
    
             tx := db.Begin()
             if err := tx.Error; err != nil {
                 c.JSON(http.StatusInternalServerError, gin.H{"error": "无法开启事务"})
                 return
             }
    
             // 关键点:范围查询 + FOR UPDATE 会触发 Next-Key 锁
             if err := tx.Raw("SELECT * FROM users WHERE age > ? FOR UPDATE", age).Scan(&users).Error; err != nil {
                 tx.Rollback()
                 c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
                 return
             }
    
             tx.Commit()
    
             c.JSON(http.StatusOK, gin.H{"message": "锁定成功", "users": users})
         }
    
         // 2. 新增用户,可能因 Next-Key 锁失败
         func addUser(c *gin.Context) {
             var user User
             if err := c.ShouldBindJSON(&user); err != nil {
                 c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"})
                 return
             }
    
             // 直接插入,可能因 Next-Key 锁导致阻塞
             if err := db.Create(&user).Error; err != nil {
                 c.JSON(http.StatusInternalServerError, gin.H{"error": "插入失败"})
                 return
             }
    
             c.JSON(http.StatusOK, gin.H{"message": "用户新增成功"})
         }
    

6 总结

  系统思维维度  		FOR UPDATE  							Next-Key 锁
  整体性 				事务锁定已存在的数据,确保一致性    防止新数据插入,确保事务隔离
  层次性 				只锁定查询到的行    				锁住查询范围的“间隙”
  动态性 				事务提交或回滚释放锁  				事务提交前,新数据无法插入
  关联性 				适用于扣库存、转账、订单处理 		 适用于防止幻读,如订单查询

减少 Next-Key 锁的使用,避免影响并发
必要时使用 innodb_locks_unsafe_for_binlog=1 禁用间隙锁
使用 FOR UPDATE 代替 Next-Key Lock,提高系统吞吐量
通过系统思维,我们可以更深入理解 MySQL Next-Key 锁 的影响,并在服务应用中合理使用锁机制,提升系统稳定性与并发能力。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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