MySQL事务隔离级别RR下Next-Key Lock解决幻读的原理
在REPEATABLE READ (RR) 隔离级别下,MySQL的InnoDB存储引擎通过Next-Key Lock机制来防止幻读现象。本文将直接拆解其工作原理,并提供验证该行为的实操步骤。
1. 理解幻读问题
首先,明确幻读的定义:在同一事务内,两次执行相同的范围查询,第二次查询返回了第一次查询未返回的“幻影”行。这些行通常是其他事务在两次查询间插入的。
在更高的隔离级别如SERIALIZABLE下,通过完全串行化可以避免幻读,但性能开销大。RR级别在保证可重复读的前提下,需要一种更精细的机制来防止幻读。
2. 锁定范围:从Record Lock到Next-Key Lock
默认的Record Lock(记录锁)只锁定查询到的具体索引记录,无法阻止其他事务在查询范围的“间隙”中插入新行。Next-Key Lock正是为了解决此问题而设计的。
观察一个没有Next-Key Lock的场景:
假设表 employees 有主键 id (1, 5, 10)。
事务A:SELECT * FROM employees WHERE id BETWEEN 5 AND 10; 返回 id=5,10。
事务B:INSERT INTO employees (id, name) VALUES (6, ‘Alice’); 成功。
事务A再次执行相同查询,将返回 id=5,6,10,发生幻读。
3. Next-Key Lock的核心构成与规则
Next-Key Lock 可以理解为 “记录锁”与“间隙锁”的结合。它锁定一个索引记录本身,以及该记录之前的间隙。
在RR级别下,对于使用索引进行范围查询的SQL语句,InnoDB会自动应用Next-Key Lock。其加锁规则遵循以下逻辑:
- 锁定查询命中的索引记录:为每一条符合条件的记录加
Record Lock。 - 锁定查询范围内的间隙:为查询条件涉及的索引区间加
Gap Lock。这确保了其他事务无法在该区间内插入新数据。 - 锁定“下一个”键之前的间隙:这是
Next-Key Lock得名的原因。它锁定记录本身以及它与前一条记录之间的间隙。例如,索引值5,10对应的Next-Key Lock锁定范围是(-∞, 5]和(5, 10]。
4. 原理演示与验证步骤
准备测试表与数据:
CREATE TABLE test (
id INT PRIMARY KEY,
value VARCHAR(50)
) ENGINE=InnoDB;
INSERT INTO test (id, value) VALUES (1, ‘A’), (5, ‘B’), (10, ‘C’);
步骤一:开启事务并执行范围查询,触发Next-Key Lock
- 打开第一个MySQL客户端连接(事务A)。
- 执行以下语句开启事务并进行范围查询:
BEGIN; SELECT * FROM test WHERE id BETWEEN 5 AND 10 FOR UPDATE;FOR UPDATE显式请求排他锁,使锁的行为更易观察。此时,事务A持有对id区间(1, 10]的Next-Key Lock(具体是(1, 5]和(5, 10],并包含id=10的记录锁)。
步骤二:在另一个事务中尝试插入,验证阻塞
- 打开第二个MySQL客户端连接(事务B)。
- 执行一条向已锁定间隙插入数据的语句:
INSERT INTO test (id, value) VALUES (6, ‘D’);观察:此语句将挂起,因为
id=6落入了事务A持有的(5, 10]间隙锁范围内。该插入操作需要获得该间隙的插入意向锁,与现有的间隙锁冲突,因此被阻塞。
步骤三:尝试插入不在锁范围内的数据
- 在事务B中,尝试插入一条不在锁定范围内的数据:
INSERT INTO test (id, value) VALUES (11, ‘E’);观察:此语句立即执行成功。因为
id=11大于锁范围的上限10,未被任何锁覆盖。
步骤四:查看当前锁信息(可选,用于深化理解)
- 在第三个连接中,查询
information_schema库中的锁表来查看事务A持有的锁:SELECT * FROM information_schema.INNODB_TRX; SELECT * FROM information_schema.INNODB_LOCKS; SELECT * FROM information_schema.INNODB_LOCK_WAITS;在输出的
INNODB_LOCKS表中,你可以看到事务A持有类型为X,REC_NOT_GAP(id=10的记录锁)、X,GAP((5, 10]的间隙锁)和X((1, 5]的Next-Key Lock)等锁。这些锁共同构成了对查询区间的完整保护。
步骤五:提交事务,释放锁
- 在事务A中,提交事务以释放所有锁:
COMMIT;观察:事务B中被阻塞的
INSERT语句(步骤二)立即执行完成。
5. 关键结论
总结如下:
- 锁定对象:
Next-Key Lock的基本单位是索引记录及其前开后闭的区间(previous key, current key]。 - 触发条件:在RR隔离级别下,使用等值或范围条件查询唯一索引或普通索引时,InnoDB会自动施加
Next-Key Lock。 - 防幻读原理:通过锁定查询可能命中的所有索引范围(包括记录和间隙),
Next-Key Lock物理上阻止了其他事务在该范围内进行插入、更新(导致索引变更)等操作。因此,事务内后续的相同范围查询,结果集是稳定、不可变的,幻读被杜绝。 - 性能权衡:这种机制在防止幻读的同时,也降低了并发插入能力。在业务设计中,应尽量使查询条件命中索引,并合理设计索引,以减小锁的范围。

暂无评论,快来抢沙发吧!