MySQL行锁、间隙锁和临键锁示例详解
作者:tanxinji
在MySQL中,InnoDB存储引擎实现了其自己的多版本并发控制(MVCC)机制,以及基于锁的并发控制。InnoDB使用锁来实现事务的隔离级别,其中包括行锁(Row Locks)、间隙锁(Gap Locks)和临键锁(Next-Key Locks)。这些锁的类型对于理解事务的隔离级别和避免死锁等问题非常重要。
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;
到此这篇关于MySQL行锁、间隙锁和临键锁示例详解的文章就介绍到这了,更多相关mysql行锁,间隙锁和临键锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
