深入理解MySQL行锁,间隙锁和临键锁
1.行锁 (Record Lock)
锁住索引中的一条具体记录。 保证原子性:防止多个事务同时修改同一条记录
例如:SELECT * FROM t WHERE id = 10 FOR UPDATE;会在 id=10这条记录的索引上加行锁。
示例
-- 事务A
BEGIN;
SELECT * FROM users WHERE id = 10 FOR UPDATE;-- 此时其他事务执行以下操作会被阻塞:
-- UPDATE users SET name = 'new' WHERE id = 10;
-- DELETE FROM users WHERE id = 10;
2.间隙锁(Gap Lock)
锁住索引记录之间的间隙,但不包括记录本身。唯一目的就是防止其他事务在这个间隙中插入新记录,解决幻读问题。
它是一个开区间。例如:假设表中有 id为 5, 10, 15 的记录。那么间隙锁可以锁住 (-∞, 5),(5, 10),(10, 15),(15, +∞)这些范围。
触发时机
(1)在可重复读或串行化隔离级别下。
(2)唯一索引/普通索引,等值查询,记录不存在时。
示例
表 user,其 age字段上有普通索引

事务A:
BEGIN;
SELECT * FROM user WHERE age = 25 FOR UPDATE; 由于 age=25不存在,InnoDB 会找到 25所在的间隙,即 (20, 30)。此时,事务 A 会在 (20, 30)这个间隙上加间隙锁。事务B:
INSERT INTO user (id, age) VALUES (10, 22); -- 阻塞。因为 22 在 (20, 30) 区间内
INSERT INTO user (id, age) VALUES (10, 28); -- 阻塞。因为 28 在 (20, 30) 区间内
INSERT INTO user (id, age) VALUES (10, 20); -- 成功。20 是已存在的记录,不在间隙锁范围内(但可能被记录锁或临键锁影响)
INSERT INTO user (id, age) VALUES (10, 30); -- 成功。30 是已存在的记录,不在间隙锁范围内事务 B 的插入操作会被阻塞,直到事务 A 提交。这样就防止了在事务 A 中如果再次执行 SELECT ... FOR UPDATE可能会看到新插入的 age=22或 28的记录(幻读)。
3.临键锁(Next-Key Lock)
行锁 + 间隙锁。它锁住一条记录和该记录之前的间隙。它是一个左开右闭区间。临键锁是 InnoDB 默认的行锁模式
例如:对于记录 id=10,它的临键锁会锁住 (5, 10]这个区间。这意味着既不能插入 id=6的新记录(间隙被锁),也不能修改或删除 id=10的记录(记录被锁)。
示例
(1)等值查询
事务A
SELECT * FROM users WHERE age = 20 FOR UPDATE;
过程:InnoDB 通过 B+ 树找到 age=20 的记录,并给它加上临键锁。
锁定的范围:(10, 20]。
为什么不是 [20, 20]?因为临键锁的本质是锁“下一个键”之前的间隙。对于 age=20,它会锁住 (上一个记录的值, 20],即 (10, 20]。
(2)范围查询
事务A
SELECT * FROM users WHERE age BETWEEN 15 AND 25 FOR UPDATE;
找到第一条满足条件的记录,即 age=20。锁住 (10, 20]。
继续向后扫描,找到 age=30。虽然 30 不满足条件(30 > 25),但根据规则,扫描到的记录也要加锁。锁住 (20, 30]。
最终锁定的范围:(10, 20] 和 (20, 30]。这相当于锁定了整个 (10, 30] 的区间。
效果:任何试图在 age=15 到 age=30 之间插入、更新记录的操作都会被阻塞。
(3)特殊场景:唯一索引等值查询
当使用唯一索引进行等值查询并且数据存在时,InnoDB 会优化,降级为仅使用行锁。
SELECT * FROM users WHERE id = 3 FOR UPDATE;
此时,InnoDB 知道 id=3 是唯一的,不需要防止幻读,所以只会锁住 id=3 这一条记录本身,而不会加间隙锁。
相关SQL语句
-- 查看当前锁信息
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;-- MySQL 8.0+ 性能schema锁监控
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
