唯一索引等值查询命中记录时只加行锁(REC_NOT_GAP),因唯一性已杜绝幻读可能;必须同时满足:使用主键或唯一二级索引、等值匹配、记录实际存在,缺一则退化失效,转为next-key lock或gap lock。

唯一索引等值查询为什么只加行锁,不加间隙锁
因为 InnoDB 判断出「仅锁定目标行本身」就足以防止幻读,没必要锁住周围间隙。退化不是“省事”,而是隔离性与并发性的精确权衡——在满足唯一性约束的前提下,WHERE id = 10 这种查询天然排除了“插入重复 id”的可能,所以锁住 id=10 这一行,就能保证事务内多次执行该查询结果一致。
什么条件下才会触发这个退化
必须同时满足两个硬性条件,缺一不可:
-
WHERE条件使用的是主键或唯一二级索引(如UNIQUE KEY phone),且该索引列值全局唯一 - 查询是等值匹配(
=或IN),并且实际查到了数据(即记录存在)
只要有一条不满足,比如用普通索引 age 查 age = 25,或者用唯一索引查 id = 999 但表里没有这行,就不会退化,而是加临键锁或间隙锁。
退化后到底锁了什么,怎么验证
退化后的锁类型是 REC_NOT_GAP(记录锁),只作用于聚簇索引上的具体记录。你可以用这条语句实时查看:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks;
执行 SELECT * FROM user WHERE id = 10 FOR UPDATE 后,查到的 LOCK_MODE 是 X,REC_NOT_GAP,LOCK_DATA 显示具体主键值(如 10),而不是区间(如 (5, 10])。
容易被忽略的边界情况
很多人以为“用了唯一索引就一定退化”,其实有三个典型反例常被忽略:
- 事务中先
UPDATE再SELECT ... FOR UPDATE,若中间有其他事务修改了该行,可能导致加锁行为变化(尤其涉及 MVCC 版本链判断) - 唯一索引字段允许
NULL,而多行同时为NULL时,MySQL 视其为“非唯一”场景,不触发退化 - 查询带
ORDER BY或LIMIT但优化器未走索引覆盖,可能引发额外扫描,导致锁范围扩大
退化的前提是“精准定位且无歧义”,任何引入不确定性(如 NULL、隐式类型转换、索引失效)的操作,都会让 InnoDB 放弃优化,回归保守的临键锁策略。










