在讨论MySQL锁机制时,许多开发者(包括一些经验丰富的老手)常常对这样一个现象感到困惑:为什么唯一索引(UNIQUE KEY)发生冲突时,MySQL会自动授予一个共享的Next-key Lock?不少人误以为这是“锁升级”或“粒度放大”,但实际上完全不是这样。这并非锁的升级,而是InnoDB在保证唯一性语义不被并发破坏的前提下,所能选择的最小安全锁定范围。从一开始,它就必须采用这种锁方式,没有其他选项。

Unique Key冲突时,为什么Record Lock无法满足需求?
如果我们仅仅锁定发生冲突的那条记录本身(即一个Record Lock),就会留下一个严重的逻辑漏洞——幻读(Phantom Read)。想象一下,另一个事务完全可以在你执行唯一性检查的间隙,在索引顺序上紧挨着冲突记录的位置插入一条新数据。这会导致唯一性检查的边界判断失效,使你无法确定“这个值在整个索引空间中是否真正独一无二”。
举个例子,假设表中已经存在name='alice'这条记录。如果只锁住它,另一个事务完全可以在B+树排序的邻近位置插入name='alicia'或name='ali'。InnoDB在执行唯一性检查时,必须确保“这个值在它应该出现的索引位置上,是真正孤立的,没有任何邻居能够干扰判断”。
因此,它必须覆盖一个更大的范围:冲突值所在的位置,以及它左侧的间隙。这个区间就是(prev_value, current_value],一个左开右闭的区间。这正是Next-key Lock的本质。
- 主键冲突:只需要一个
S Record Lock即可。为什么?因为主键是聚簇索引,物理顺序就是逻辑顺序,值本身唯一且决定了行在物理存储上的位置。不存在“邻近插入干扰唯一性”这种问题。 - 唯一二级索引冲突:必须加
Next-key Lock。因为这是非聚簇的二级索引,值在B+树中虽然有序,但物理存储是分散的。必须依靠间隙锁定来防止“错位插入”导致的唯一性破坏。 - 无论隔离级别:即使你使用的是
READ COMMITTED,这个行为也不会退化。MySQL的文档写得明明白白:唯一二级索引冲突,加锁必须是Next-key Lock,这是硬性规定。
INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 同样无法逃脱
很多开发者会想,如果使用了INSERT IGNORE或者ON DUPLICATE KEY UPDATE,是不是就能绕过这个看似“重”的锁?答案是否定的。这些语法只是让SQL语句不报错而已,但唯一性检查阶段该走的流程一步都不会少,该加的锁一个也不会省。
事务在决定是插入还是更新之前,必须先申请一个S Next-key Lock,确认“这个地方没人占着”或者“我准备接手”。
- 两个事务同时执行
INSERT IGNORE,如果它们插入的值在索引顺序上刚好相邻,比如一个要插'alice',另一个要插'bob',那么它们各自申请的S Next-key Lock可能会覆盖到重叠的间隙,从而形成S锁的等待链。 - 更危险的是混合操作。比如一个事务刚拿到
S Next-key Lock,紧接着又执行一个UPDATE ... WHERE name = ?,试图升级为X锁。而此时另一个事务也持有同样的S锁且也想升级,死锁就很容易发生了。 - 对于
ON DUPLICATE KEY UPDATE,它在冲突路径上的流程是“先S锁查,再X锁改”,其锁行为和INSERT ... SELECT ... FOR UPDATE几乎一模一样。
死锁的关键不在于锁太重,而在于S锁和X锁争夺同一把Next-key
典型的死锁场景,并不是两个INSERT直接撞上同一条记录。而是这样:事务A拿到了索引uk_name上'alice'的S Next-key Lock,接着它想去更新,就需要申请X锁。事务B也拿到了同样的S Next-key Lock(S锁之间是兼容的),然后它也想升级为X锁。这时候,双方都在等对方释放S锁,好让自己能升级成X锁。InnoDB检测到这个循环等待,就会回滚其中一个事务。
- 关键点在于:
S Next-key Lock本身不阻塞其他S锁,但它会阻塞所有X锁的请求。 - 这个锁是加在二级索引页上的,不会自动蔓延到主键索引,除非后续的SQL语句显式地访问了主键(比如
UPDATE条件里带了主键)。 - 排查死锁日志时,记得留意
lock_mode S waiting这个字段。它明确告诉你,等待的是共享的Next-key锁,而不是间隙锁或纯记录锁。
真正容易被忽略的核心是:这个锁的行为,与你的隔离级别无关,与你是否用了IGNORE或UPDATE这些特定SQL写法无关,只取决于一点:你是否在唯一二级索引上做了等值唯一性检查。 只要你的写入操作涉及UNIQUE KEY列,就必须按Next-key Lock的逻辑,来准备应对S锁和X锁交叉等待的局面。
