Next-Key Lock 是 Record Lock 与 Gap Lock 的组合,仅在 REPEATABLE READ 隔离级别下由 InnoDB 自动启用;需通过 performance_schema.data_locks 表中 LOCK_MODE = 'X'(配合范围查询和 LOCK_DATA 显示区间)及阻塞现象反推确认,而非直接显示。

如何确认当前事务是否持有 Next-Key Lock
Next-Key Lock 不是独立锁类型,而是 Record Lock + Gap Lock 的组合表现,只在 REPEATABLE-READ 隔离级别下由 InnoDB 自动启用。它不会直接出现在 INNODB_LOCKS 表中(该表已弃用),也不能靠 SHOW ENGINE INNODB STATUS 的“LOCK WAIT”段落肉眼识别——你得从锁的 范围行为 和 阻塞现象 反推。
必须查 performance_schema.data_locks 表
MySQL 5.7+ 后,INFORMATION_SCHEMA.INNODB_LOCKS 已被标记为废弃,真实、可信赖的锁信息只来自 performance_schema.data_locks。但要注意:该表默认可能未启用,需先确认并开启:
-
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';—— 确保相关 instrument 是ENABLED UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'global_instrumentation';- 重启会话或执行
FLUSH TABLES;生效(部分版本需)
之后运行:
SELECT ENGINE_TRANSACTION_ID AS trx_id, OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, -- 值为 'RECORD' 表示行级锁(含 Record/Gap/Next-Key) LOCK_MODE, -- 关键!'X,GAP' 是间隙锁,'X,REC_NOT_GAP' 是记录锁,'X' 或 'X,ORDINARY' 通常对应 Next-Key 锁 LOCK_DATA -- 若为范围值(如 '5', '10'),且 LOCK_MODE 是 'X',大概率是 Next-Key 锁作用于 (5,10] FROM performance_schema.data_locks WHERE LOCK_TRX_ID = (SELECT TRX_ID FROM information_schema.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID());
为什么 LOCK_MODE = 'X' 就代表 Next-Key Lock?
这不是绝对等价,而是 InnoDB 在 RR 级别下对范围条件加锁时的默认行为。当出现以下情况,LOCK_MODE = 'X' 几乎可以断定是 Next-Key Lock:
- 查询语句带范围条件(如
WHERE seat_id > 3 AND seat_id ),且命中了索引 -
INDEX_NAME非 NULL(说明走索引,不是全表扫描) -
LOCK_DATA显示的是一个具体值(如5),但同时该索引上存在更小的前驱值(如已有seat_id = 3),InnoDB 就会锁住 (3,5] 区间 - 并发插入恰好卡在该区间内(如插
seat_id = 4)被阻塞,即验证成功
注意:LOCK_MODE = 'X,GAP' 是纯 Gap Lock(无 Record Lock),LOCK_MODE = 'X,REC_NOT_GAP' 是纯 Record Lock,只有 'X' 或 'X,ORDINARY' 才是 Record + Gap 的组合体 —— 即 Next-Key。
容易忽略的三个前提条件
Next-Key Lock 不是“一查就出”,它依赖底层环境是否满足触发条件:
- 事务隔离级别必须是
REPEATABLE-READ(检查SELECT @@tx_isolation;,不是READ-COMMITTED) - 语句必须是锁定读(
SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE)或 DML(UPDATE/DELETE),普通SELECT不加 Next-Key - WHERE 条件必须使用**有索引的列**;若走全表扫描,InnoDB 会退化为锁整张表(表锁)或大量行锁,而非 Next-Key
最常踩的坑是:开发同学在测试时用了 READ-COMMITTED 级别,或字段没建索引,却还在日志里找 X,ORDINARY —— 那根本不会出现。


















