MySQL在RC隔离级别下唯一索引冲突时加Next-Key锁,是为了防止并发插入破坏唯一约束,即使RC通常禁用间隙锁,但官方明确保留该例外以避免bug#68021类唯一性失效。

MySQL在RC隔离级别下**通常不使用Next-Key Lock**,但存在明确的例外场景——当操作涉及**唯一索引 + 唯一约束检查失败(如INSERT/REPLACE冲突、DELETE未命中)时,InnoDB仍会加Next-Key Lock**。这不是设计疏漏,而是为防止唯一性破坏而保留的底层锁行为。
唯一索引插入冲突时强制加Next-Key Lock
RC下普通查询或UPDATE不会触发间隙锁,但遇到唯一键冲突时,InnoDB必须确保“同一值不能被两个事务同时插入成功”。此时它会在冲突值对应的位置加X型next-key lock(而非仅record lock),覆盖该值所在间隙+记录本身。
-
INSERT INTO t (id, name) VALUES (5, 'a'):若id是唯一索引且已有id=5,则事务会在(4,5]或(5,6]范围加X next-key lock(取决于索引分布) -
REPLACE INTO和INSERT ... ON DUPLICATE KEY UPDATE同样触发该行为,因为它们内部先做唯一性查找再决定插入或更新 - 这个锁不是为了防幻读,而是为了串行化唯一性校验过程——否则多个事务可能同时通过“查无此值”判断,然后一起插入,破坏约束
DELETE未命中唯一索引时也可能加Next-Key Lock
执行DELETE FROM t WHERE id = 5,而表中根本不存在id=5的记录,在RC下本应“不加任何锁”,但实际会加S型next-key lock在(4,5]或(5,6]上(取决于相邻索引值)。这是为了阻止其他事务在此间隙插入id=5,从而保证“删除不可见”语义与唯一约束一致。
- 只发生在**唯一索引等值查询且无匹配行**时;普通索引或非唯一字段不会触发
- 锁类型是S(共享)而非X,所以多个事务可并发等待该锁,但一旦有事务插入
id=5,其余事务的DELETE会因唯一冲突再次触发X next-key lock - 可通过
SELECT * FROM performance_schema.data_locks确认锁范围
为什么RC下不彻底去掉Gap Lock?
早期MySQL曾尝试在RC下完全移除唯一约束相关的间隙锁,结果触发了bug#68021:两个并发INSERT在删除后几乎同时通过唯一性检查,导致重复插入。根本原因在于唯一性校验与插入动作之间存在微小时间窗口,没有锁保护就会竞态。
- RC下禁用Gap Lock是为了提升并发性能,但唯一约束是数据完整性底线,宁可牺牲一点并发也不能妥协
- 这种Next-Key Lock是“最小必要”形式:只在唯一索引上、只在冲突/未命中路径上、范围严格限定在相邻键之间
- 如果你看到RC事务锁住大片范围,大概率是误用了非唯一索引、或条件没走索引导致全表扫描+隐式锁升级
真正容易被忽略的是:这个机制只对**唯一索引生效**,且只在**DML触发唯一性检查环节**才激活。别指望SELECT ... FOR UPDATE在RC下产生Next-Key Lock——它只会加Record Lock,除非你手动升到RR级别。


















