临键锁锁的是索引记录本身及其前一索引值之间的左开右闭区间,即record lock+gap lock组合,仅在repeatable read下启用,如索引1、5、10中对5加锁即锁定(1,5]。

临键锁(Next-Key Lock)到底锁了什么
临键锁不是一种独立锁,而是记录锁(Record Lock)+ 间隙锁(Gap Lock)的组合体,只在 REPEATABLE READ 隔离级别下默认启用。它锁定的是「索引记录本身 + 该记录前一个索引值之间的整个左开右闭区间」。比如主键为 1,5,10,执行 SELECT * FROM t WHERE id > 5 AND id ,InnoDB 实际会锁住 (5,10] 这个范围——既防止修改 id=10 的行,也阻止插入 id=6、7、8、9 的新行。
关键点在于:哪怕查询条件没命中任何数据(如 WHERE id = 7 但表中无该值),只要走的是范围扫描或非唯一索引,InnoDB 仍可能加临键锁,而非单纯不加锁。
为什么临键锁容易引发死锁或锁等待
临键锁扩大了锁定范围,尤其在范围查询、非唯一索引更新、ORDER BY + LIMIT 组合场景下,极易产生不可预期的锁交集。典型表现:
- 两个事务同时执行
UPDATE user SET status=1 WHERE create_time > '2026-08-01' AND status=0,即使修改的行不重叠,也可能因间隙重叠而互相阻塞 - 事务 A 按
id IN (3,1,2)更新,事务 B 按id IN (2,3,1)更新,InnoDB 内部按主键升序加锁,但若 WHERE 条件未走索引,就会退化为全表扫描+大量临键锁,大幅提升冲突概率 -
SELECT ... FOR UPDATE在非唯一索引上执行等值查询(如WHERE name='张三'),会锁住所有匹配 name 的行及其前后间隙,而非单行
避免临键锁冲突的实操手段
核心思路是「缩小锁粒度」和「消除不确定性」,而不是盲目调低隔离级别:
- 确保 WHERE 条件能命中唯一索引或主键:用
id = ?替代name = ?,或为高频查询字段补联合唯一索引 - 范围查询尽量收窄:把
create_time > '2026-01-01'改为create_time BETWEEN '2026-01-01' AND '2026-08-13',减少间隙覆盖 - 显式排序 + 分页控制:批量更新时强制
ORDER BY id,避免 InnoDB 内部加锁顺序不可控;用LIMIT时确认是否触发临键锁(SELECT ... FOR UPDATE LIMIT 1仍可能锁住后续间隙) - 评估是否真需 RR 级别:若业务允许幻读,可降级到
READ COMMITTED,此时 InnoDB 不使用间隙锁和临键锁,仅保留记录锁
调试时怎么确认是不是临键锁惹的祸
光看 SQL 很难判断,必须结合执行计划与锁状态交叉验证:
- 执行
EXPLAIN,检查key是否为有效索引、rows是否远大于实际影响行数(暗示扫描了大量间隙) - 复现卡顿后立即运行
SHOW ENGINE INNODB STATUS\G,在LATEST DETECTED DEADLOCK或TRANSACTIONS区块里找lock_mode X locks gap before rec或lock_mode X locks rec but not gap这类描述 - 用
performance_schema.data_locks查实时锁信息(MySQL 8.0+),过滤LOCK_DATA和LOCK_MODE字段,识别是否出现REC_NOT_GAP与GAP并存
临键锁的隐蔽性在于:它不总表现为“锁某一行”,而常体现为“不让插某几条”,这种限制在日志和监控里极难直接感知,必须从索引设计和查询写法源头控制。











