MySQL在唯一二级索引冲突时加S Next-key Lock,是为防止邻近插入破坏唯一性,需锁定(prev_value, current_value]区间;主键冲突只需S Record Lock;该行为与隔离级别及SQL写法无关。
MySQL在唯一二级索引(UNIQUE KEY)冲突时加S Next-key Lock,不是为了“升级”,而是它本来就要这么锁——这是保证唯一性语义不被并发破坏的最小安全粒度。
只锁住冲突那条记录(Record Lock)会留下幻读级漏洞:另一个事务仍可在相同索引值排序邻近的位置插入新行,干扰唯一性检查的边界判断。比如name='alice'已存在,若只锁该记录,别人还能插name='alicia'或name='ali'(按B+树顺序紧挨着),而InnoDB在做唯一性校验时必须确保“这个值在整个索引位置上是孤立的”,否则可能漏判冲突。
所以必须覆盖“该值所在位置 + 左侧间隙”,即(prev_value, current_value]这个左开右闭区间——这就是Next-key Lock的本质。
PRIMARY KEY冲突只需S Record Lock:因为主键是聚簇索引,值唯一且物理顺序即逻辑顺序,不存在“邻近插入干扰唯一性”的问题UNIQUE KEY是非聚簇二级索引,B+树中值有序但物理存储分散,必须靠间隙锁定防止“错位插入”READ COMMITTED隔离级别,这个行为也不退化——MySQL文档明确要求唯一二级索引冲突必须加Next-key Lock
很多人以为加了IGNORE或ON DUPLICATE KEY UPDATE就能绕过锁,其实不然:它们只是让SQL不报错,但唯一性检查阶段该加的锁一个不少。事务仍要先申请S Next-key Lock确认“这里没别人占着”,才能决定是插入还是更新。
INSERT IGNORE INTO t (name) VALUES ('alice')和INSERT IGNORE INTO t (name) VALUES ('bob'),如果'alice'和'bob'在索引顺序上相邻(比如B+树里'alice'在'bob'左边),它们各自申请的S Next-key Lock可能覆盖重叠间隙,形成S锁等待链INSERT IGNORE持S锁后马上跟UPDATE ... WHERE name = ?,后者要X锁,就极易和另一个同模式事务构成死锁环ON DUPLICATE KEY UPDATE在冲突路径上同样走“先S锁查、再X锁改”的流程,锁行为和INSERT ... SELECT ... FOR UPDATE几乎一致典型死锁不是两个INSERT直接撞上同一条记录,而是:事务A拿到uk_name上'alice'的S Next-key Lock,接着申请X锁去更新;事务B也拿到同样的S锁(S-S兼容),再申请X锁——此时双方都在等对方释放S锁以升级为X锁,InnoDB检测到循环等待后回滚其一。
S Next-key Lock本身不阻塞其他S锁,但会阻塞所有X锁请求UPDATE带主键条件)lock_mode S waiting字段——它明确告诉你等的是共享Next-key锁,不是间隙锁也不是纯记录锁真正容易被忽略的是:这个锁行为与隔离级别无关,与SQL写法(IGNORE/UPDATE)无关,只取决于“是否在唯一二级索引上做等值唯一性检查”。只要涉及UNIQUE KEY列的写入,就得按Next-key逻辑准备应对S/X锁交叉等待。