表级锁-间隙锁/临键锁

1、锁定索引记录间隙(不包括该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读,在RR隔离级别下支持。
例:id为索引,6-12之间的索引中添加间隙锁。

2、索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
举例:

CREATE TABLE user (
  id INT PRIMARY KEY,
  name VARCHAR(100)
) ENGINE=InnoDB;

id是主键。

BEGIN;
SELECT * FROM user WHERE id = 3 FOR UPDATE;

如果 id = 3 存在,InnoDB 会:

  • 给这条记录加“行锁”(Record Lock);
  • 因为是唯一索引等值查询,只锁命中的那一行;
  • 不会产生间隙锁。

不存在时的锁
如果 id = 3 不存在,那么:

  • InnoDB 会加一把间隙锁(Gap Lock),锁定“id = 3 应该插入的位置”的范围;
  • 假设当前表中已有数据为:id = 1, 2, 5, 7;
  • id = 3 落在 (2, 5) 的间隙中;
  • 那么 SELECT ... FOR UPDATE 会给 (2, 5) 加一把间隙锁,防止其他事务在这里插入 3、4 之类的值

为什么要这样做?
这是为了防止幻读(Phantom Read):

  • 如果不加锁,当前事务后续可能会再次查 id = 3,而其他事务可能插入了;
  • 所以先用“间隙锁”锁住这个“空位”,保护范围内不能插入新数据;
  • 即使记录本身不存在,也能防止“插入后变得存在”。

普通索引

因为普通索引不具备唯一性,因此普通索引加锁会是:行锁+间隙锁。
举例:

SELECT * FROM user WHERE name = 'Alice' FOR UPDATE;

name 是普通索引
1、命中一条记录,InnoDB加行锁 + 间隙锁。

  • 行锁在 'Alice'
  • 间隙锁在相邻值之间的范围,用于防止幻读(比如 'Alice' 被重复插入)

2、如果记录不存在。

SELECT * FROM user WHERE name = 'Daniel' FOR UPDATE;

加 间隙锁:锁住 'Daniel' 该在的位置,比如 'Charlie' 和 'Edward' 之间的 gap
延伸:联合索引情况也类似
例如 INDEX(name, age),如果你只使用 name='Alice' 查询,由于不满足最左前缀匹配原则,仍按普通索引规则处理。

posted @ 2025-08-01 18:26  Charlie-Pang  阅读(54)  评论(0)    收藏  举报