next-key lock 总在“看不见的地方”加锁,因其本质是 record lock + gap lock 组合,锁定左开右闭区间 (prev_value, current_record],范围取决于索引树真实前驱节点而非 where 条件值。

Next-Key Lock 为什么总在“看不见的地方”加锁
加了索引 ≠ 只锁命中行。InnoDB 在 REPEATABLE READ 隔离级别下,只要走索引(尤其是非唯一索引),默认用 Next-Key Lock —— 它是 Record Lock + Gap Lock 的组合,锁定的是左开右闭区间 (prev_value, current_record]。这个“前一个值”不是凭空来的,它取决于**索引树中真实存在的前驱节点**,而不是 WHERE 条件里写的数字。
常见误判:以为 WHERE id > 10 AND id 只锁 11–19 行;实际可能锁住 <code>(10, 20],而如果表里根本没有 id = 10,那前驱可能是更早的索引值(比如 5),导致锁范围意外扩大。
- 唯一索引等值查询(如
WHERE name = '吕布')只加Record Lock,不加Gap Lock - 非唯一索引等值查询(如
WHERE score = 89)会加Next-Key Lock,哪怕只查到一行 - 范围查询(
>,, <code>BETWEEN)必然触发Next-Key Lock,且边界由索引结构动态决定 -
SELECT ... FOR UPDATE或UPDATE/DELETE走非唯一索引时,间隙部分极易与其他事务交叉
死锁复现的关键:两个事务对同一组间隙“争抢插入权”
典型场景是并发插入或更新带范围条件的记录。例如两张表都依赖 score 字段做业务判断,而 score 上只有普通索引(非唯一):
事务 A 执行:UPDATE sc SET class = '二年一班' WHERE score BETWEEN 70 AND 85;
事务 B 同时执行:INSERT INTO sc (name, class, score) VALUES ('马超', '一年三班', 75);
这时 A 锁住的是类似 (65, 70], (70, 85], (85, 90] 这些 Next-Key 区间;B 插入 75 时,必须检查 (70, 85] 是否被锁 —— 如果 A 还没提交,B 就卡住;若此时 B 已先锁住某个 gap(比如通过另一条语句),A 又反过来需要 B 占着的间隙,就构成循环等待。
- 间隙本身不可见,
SELECT * FROM performance_schema.data_locks中LOCK_DATA显示成对值(如70, 85)才是关键线索 - 联合索引上部分匹配(如
WHERE pool_id = 100 AND identifier IS NULL)也会触发间隙锁,即使identifier是 NULL -
INSERT ... ON DUPLICATE KEY UPDATE在唯一冲突时,会对冲突值加Record Lock,但之前扫描过程仍可能持有Gap Lock
怎么验证当前 SQL 到底锁了哪些间隙
别猜,直接查 performance_schema.data_locks(MySQL 8.0+)。重点看三列:INDEX_NAME、LOCK_TYPE、LOCK_DATA:
LOCK_TYPE = 'RECORD' 表示只锁行;LOCK_TYPE = 'RECORD' 但 LOCK_DATA 是成对值(如 100, 105),基本就是 Next-Key Lock;LOCK_TYPE = 'TABLE' 是表锁征兆,通常说明索引失效被迫全表扫。
- 执行前先开事务:
BEGIN;,再跑你的目标 SQL(避免自动提交干扰) - 立刻查:
SELECT INDEX_NAME, LOCK_TYPE, LOCK_DATA FROM performance_schema.data_locks WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'sc'; - 注意
LOCK_DATA为空时,可能锁的是 supremum(最大值之后的间隙),显示为supremum pseudo-record - 如果看到多个事务在同一
INDEX_NAME下持有不同LOCK_DATA区间,且存在重叠或首尾相接,就是死锁温床
绕过 Next-Key Lock 的实用限制条件
不是所有场景都能删索引或改隔离级别,但可以精准控制锁行为:
- 把非唯一索引改成唯一索引(前提是业务允许),等值查询就退化为纯
Record Lock - 用
SELECT ... LOCK IN SHARE MODE替代FOR UPDATE,降低锁强度(但要注意一致性读逻辑是否适配) - 显式加
ORDER BY ... LIMIT 1配合唯一条件,让优化器选更窄的索引路径,减少扫描范围 - 在应用层做幂等控制,用
INSERT IGNORE或ON DUPLICATE KEY UPDATE替代先查后插,避免两次加锁 - RR 级别下无法禁用间隙锁,但可临时切到
READ COMMITTED(需评估幻读风险):SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
真正难处理的不是锁本身,而是 Next-Key Lock 的边界完全依赖实时索引分布 —— 同一条 SQL,在数据量增长、索引分裂、页合并之后,锁范围可能完全不同。线上排查时,永远要结合 SHOW CREATE TABLE 和 EXPLAIN FORMAT=tree 看实际走哪个索引、是否用了索引下推,再对照 data_locks 输出,才能定位到那个“看不见的间隙”。











