在服务开发中使用间隙锁

举报
码乐 发表于 2025/02/07 09:18:25 2025/02/07
【摘要】 1 简介间隙锁Gap 锁 是 MySQL 的 InnoDB 存储引擎中使用的一种特殊锁,用于锁定索引范围中的空隙,防止其他事务插入数据到该范围中。它是 InnoDB 存储引擎在实现 可重复读 隔离级别时使用的锁。Gap 锁的使用可以有效避免在 可重复读 隔离级别下出现幻读(即事务读取到的数据,在同一事务内发生了变化),并且可以避免插入死锁的发生。 2. 什么是 Gap 锁Gap 锁(间隙锁...

1 简介

间隙锁Gap 锁 是 MySQL 的 InnoDB 存储引擎中使用的一种特殊锁,用于锁定索引范围中的空隙,防止其他事务插入数据到该范围中。它是 InnoDB 存储引擎在实现 可重复读 隔离级别时使用的锁。

Gap 锁的使用可以有效避免在 可重复读 隔离级别下出现幻读(即事务读取到的数据,在同一事务内发生了变化),并且可以避免插入死锁的发生。

2. 什么是 Gap 锁

Gap 锁(间隙锁)详解

(1) 定义

Gap 锁(间隙锁)是 MySQL InnoDB 存储引擎 在可重复读(REPEATABLE READ)隔离级别下,为了防止 幻读(Phantom Read) 而使用的一种锁定机制。它的主要作用是:

锁定索引范围的空隙(Gap),即某些不存在的行,防止其他事务向该范围插入数据。
确保查询结果的一致性,避免同一事务中两次查询得到不同的结果。

(2) 设计目的

防止幻读:在 REPEATABLE READ 隔离级别下,防止其他事务在某个索引范围内插入新记录,导致当前事务两次查询结果不一致。

避免数据插入:即使某些数据不存在,Gap 锁仍然会锁住这些空隙,防止其他事务插入新数据。

3 对比Gap 锁 及其适用场景

    对比项 Gap 锁(间隙锁)  全局锁(Global Lock)
    作用范围    索引范围内的空隙    整个 MySQL 实例
    影响范围    仅影响某些索引范围的插入操作  影响所有数据库的写操作
    锁定类型    行级锁的一部分(索引级锁)   服务器级锁
    适用场景    可重复读隔离级别,防止幻读   备份、迁移
    影响的 SQL 操作  INSERT、UPDATE、DELETE    所有写操作
  • Gap 锁的使用场景

(1) 防止幻读

事务 1 先执行 SELECT … FOR UPDATE,如果没有 Gap 锁,事务 2 可能会在这个范围插入新数据,导致事务 1 读出的数据发生变化(幻读)。

(2) 确保范围查询的一致性

当执行范围查询(如 WHERE age > 20 FOR UPDATE),Gap 锁会锁定索引范围内的空隙,防止其他事务插入 age=21 的新记录。

4. MySQL 中 Gap 锁的使用示例

(1) 创建测试表

  CREATE TABLE employees (
      id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(50) NOT NULL,
      age INT NOT NULL,
      INDEX idx_age (age)  -- 创建索引
  ) ENGINE=InnoDB;

(2) 插入初始数据

		INSERT INTO employees (name, age) VALUES ('Alice', 25), ('Bob', 30), ('Charlie', 35);

(3) 测试 Gap 锁

事务 1:执行范围查询

    START TRANSACTION;
    SELECT * FROM employees WHERE age BETWEEN 25 AND 35 FOR UPDATE;

这条 SQL 语句将执行以下动作:

锁住已有数据(age = 25, 30, 35)。
锁住 age ∈ (25, 35) 这个范围的“空隙”,防止其他事务插入 age=26, 27,… 34 的新记录。

事务 2:尝试插入数据

	INSERT INTO employees (name, age) VALUES ('David', 28);

将被阻塞!

由于 28 处于 25-35 的范围,Gap 锁阻止插入,直到事务 1 提交或回滚。

事务 1 提交

		COMMIT;

事务 2 现在可以继续执行插入。

4 在web服务中使用间隙锁

(1) 代码示例

    var db *sql.DB

    func init() {
        var err error
        dsn := "root:password@tcp(127.0.0.1:3306)/testdb?parseTime=true"
        db, err = sql.Open("mysql", dsn)
        if err != nil {
            log.Fatal(err)
        }
        db.SetMaxOpenConns(10)
        db.SetMaxIdleConns(5)
    }

    // 事务 1: 查询数据并加 Gap 锁
    func transaction1(c *gin.Context) {
        tx, err := db.Begin()
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "事务开启失败"})
            return
        }

        // 执行查询并加锁
        _, err = tx.Exec("SELECT * FROM employees WHERE age BETWEEN 25 AND 35 FOR UPDATE")
        if err != nil {
            tx.Rollback()
            c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
            return
        }

        // 模拟长时间事务
        time.Sleep(10 * time.Second)

        tx.Commit()
        c.JSON(http.StatusOK, gin.H{"message": "事务 1 执行完成"})
    }

    // 事务 2: 插入数据
    func transaction2(c *gin.Context) {
        _, err := db.Exec("INSERT INTO employees (name, age) VALUES ('David', 28)")
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "插入失败,可能被 Gap 锁阻止"})
            return
        }

        c.JSON(http.StatusOK, gin.H{"message": "插入成功"})
    }

    func main() {
        r := gin.Default()
        r.GET("/tx1", transaction1) // 执行事务 1(锁定范围)
        r.GET("/tx2", transaction2) // 执行事务 2(尝试插入)
        r.Run(":8080")
    }

5. 小结论

(1) 间隙锁的作用

主要用于防止幻读,确保范围查询的一致性。
锁住索引范围的“空隙”,防止其他事务插入新数据。

(2) Gap 锁 vs. 全局锁

    对比项 		Gap 锁(间隙锁)  	全局锁(Global Lock)
    作用范围    某个索引范围  			整个 MySQL 服务器
    影响操作    INSERT(防止幻读)    所有写操作
    适用场景    可重复读隔离级别    	备份、迁移
    加锁方式    行级索引锁 + Gap 锁   	服务器级锁

Gap 锁 主要用于 防止并发事务中的幻读,只影响索引范围的插入操作,不会影响查询和更新。
全局锁 则是 影响整个 MySQL 实例,用于备份、数据迁移等场景。

API 结合 Gap 锁 可以 模拟事务的加锁机制,防止不一致的数据插入。
当你的业务需要 严格控制事务范围内的数据一致性(如银行转账、订单处理),Gap 锁是非常关键的锁机制!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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