记录锁只锁命中行且需主键或唯一索引;间隙锁锁空隙防幻读,仅rr级别生效;临键锁=记录锁+左侧间隙锁,为rr默认行锁机制。

记录锁只锁命中的那一行,但前提是走主键或唯一索引
记录锁(Record Lock)是最轻量的行级锁,它不碰间隙、不扩范围,就死死锁住索引中**实际存在的某一条记录**。它的存在意义很直接:防止其他事务改或删这一行。
常见错误现象是以为 WHERE id = 100 一定只锁一行——其实不然。如果 id 没建索引,InnoDB 会全表扫描,退化为隐式主键逐行加锁,效果接近表锁;如果 id 是普通索引且存在重复值,还可能触发临键锁逻辑,锁住不止一行。
- 正确用法:确保查询条件命中主键或唯一索引,例如
UPDATE users SET name='a' WHERE id=100(id是主键) - 性能影响:开销最小,对并发最友好
- 容易踩的坑:在非唯一索引上执行等值查询(如
WHERE status=1),即使只查到 1 行,也可能锁住多个临键区间
间隙锁只锁“空隙”,不锁记录本身,RR 隔离级别下默认启用
间隙锁(Gap Lock)锁定的是两个索引记录之间的“空档”,比如索引值有 5 和 10,那 (5, 10) 就是一个典型间隙。它不关心这个范围内有没有数据,只阻止别人往里面插新记录。
典型使用场景是防止幻读:你在事务里查 SELECT * FROM t WHERE age BETWEEN 20 AND 30 FOR UPDATE,InnoDB 不仅锁已有数据,还会在 (20, 30) 这个间隙上加锁,避免其他事务插入 age=25 的新行。
- 触发条件:范围查询(
>,, <code>BETWEEN)、等值查询但目标记录不存在(如WHERE age=25,但表里没有age=25的行) - 隔离级别限制:仅在
REPEATABLE READ下生效;READ COMMITTED下会被禁用,间隙锁不生效 - 容易踩的坑:辅助索引(非唯一)上的等值查询,哪怕只查一个存在值,也会先锁住左间隙,再锁右间隙(即锁两个 gap),不是直觉上的“只锁一个点”
临键锁 = 记录锁 + 左侧间隙锁,是 RR 级别下的默认行锁算法
临键锁(Next-Key Lock)不是独立锁类型,而是 InnoDB 在 REPEATABLE READ 下对行锁的默认实现方式:它把一个记录锁和它左边的间隙锁打包在一起,形成左开右闭区间,比如 (5, 10] —— 锁住 10 这条记录,也锁住 5 到 10 之间的所有空隙。
你几乎无法“关闭”它,除非降级隔离级别或改写 SQL。比如 SELECT * FROM t WHERE id > 5 FOR UPDATE,若索引中下一个值是 10,那实际锁的是 (5, 10];若后面没值了,就变成 (5, +∞)。
- 为什么设计成这样:单靠记录锁防不了幻读,单靠间隙锁又没法保护边界记录本身,合起来才能覆盖“范围查询 + 新增记录”的全部风险面
- 参数差异:
innodb_locks_unsafe_for_binlog已被弃用,不要试图靠它关临键锁;真正可控的是隔离级别和索引结构 - 容易踩的坑:在业务中用
SELECT ... FOR UPDATE做并发控制时,以为只锁结果集,实际可能锁住大片空白间隙,导致无关插入被阻塞
怎么验证当前 SQL 加了哪种锁?看 INFORMATION_SCHEMA.INNODB_TRX 和 INNODB_LOCKS 不够准
MySQL 8.0+ 已移除 INNODB_LOCKS 表,真实加锁行为得靠 SELECT * FROM performance_schema.data_locks 查看。但更实用的办法是构造可复现场景,配合 SHOW ENGINE INNODB STATUS\G 中的 LATEST DETECTED DEADLOCK 或 TRANSACTIONS 部分观察 lock_mode 和 lock_type。
例如,在事务 A 中执行 UPDATE t SET x=1 WHERE number = 5(number 是普通索引),然后在事务 B 尝试插入 number=4 或 number=6,看是否被阻塞——就能反推是否产生了间隙或临键锁。
- 关键提示:explain 只能看是否走索引,看不出锁类型;锁行为取决于运行时索引数据分布,不是 SQL 字面意思
- 最常被忽略的一点:辅助索引的临键锁会“携带”主键值参与排序,所以相同
number值下,不同id会导致间隙划分不同,锁范围不可预测











