主键等值冲突加s,rec_not_gap记录锁,仅锁记录本身;唯一二级索引冲突加s,next-key锁,含记录及左侧间隙,rc级别也不退化。

主键等值冲突时加 rec_not_gap 类型的 Record Lock,唯一二级索引冲突时加的是 S, next-key 锁,不是纯 Record Lock。
主键冲突只锁记录本身,lock_mode 显示 rec_not_gap
当 INSERT 或 UPDATE 触发主键重复(比如 id = 5 已存在),InnoDB 会在主键索引上加一个 S 型 Record Lock,lock_mode 字段为 S,rec_not_gap:
- 它不扩散到间隙,不会阻塞附近范围的插入
- 多个事务可以同时对同一条冲突主键加 S 锁(S-S 兼容),所以
INSERT IGNORE并发不会直接死锁 - 但会阻止其他事务对该行加 X 锁(比如
UPDATE或DELETE),防止“一边报错一边被删”导致业务逻辑混乱 - 查锁必须用
performance_schema.data_locks,SHOW ENGINE INNODB STATUS\G里只显示S lock on the record,不写明是否带 gap
唯一二级索引冲突加的是 S Next-key 锁,不是 Record Lock
在 UNIQUE KEY name(name) 上插入重复值(如 name = 'alice' 已存在),实际加的是 S,next-key 锁,覆盖记录 + 左侧间隙:
- 即使隔离级别是
READ COMMITTED,这个行为也不会退化——MySQL 官方文档明确说明唯一索引冲突不因 RC 而跳过 gap 部分 - 锁对象是二级索引项本身,不是主键;但隐式地,InnoDB 还会顺着该二级索引查到对应主键,并在主键索引上也加一把 S Record Lock(用于一致性读)
-
lock_data字段可能显示类似'alice', 1(值 + 主键 ID),说明锁落在二级索引记录上 - 这是幻读防护的代价:防止另一个事务在
'alice'前后插入新行,干扰唯一性语义边界
INSERT IGNORE 并发时死锁常源于 S Next-key 与 X Next-key 的交叉等待
看似“忽略”的操作,底层仍抢锁。典型死锁链:
- 事务 A 执行
INSERT IGNORE INTO t (name) VALUES ('alice')→ 拿到uk_name上的S,next-key锁 - 事务 B 同样执行相同语句 → 也拿到
S,next-key锁(S-S 兼容,不阻塞) - 事务 A 紧接着执行
UPDATE t SET level = 1 WHERE name = 'alice'→ 申请X,next-key锁,被事务 B 的 S 锁挡住 - 事务 B 同样执行 UPDATE → 也被事务 A 的 S 锁挡住 → InnoDB 检测到循环等待,回滚其一
这种死锁在 REPLACE INTO 中更易触发,因为它要先删后插,涉及主键和二级索引两把 X 锁,锁范围更大、持有时间更长。
验证当前加的是 Record Lock 还是 Next-key 锁,只看 lock_mode 字段
查 performance_schema.data_locks 是唯一可靠方式:
-
lock_mode = 'S,rec_not_gap'或'X,rec_not_gap'→ 纯 Record Lock,没锁间隙 -
lock_mode = 'S,gap'或'X,gap'→ 纯间隙锁(比如等值查询未命中) -
lock_mode = 'S,next-key'或'X,next-key'→ 临键锁,Record + Gap 组合 -
INFORMATION_SCHEMA.INNODB_TRX和SHOW ENGINE INNODB STATUS\G都不区分 rec_not_gap 和 next-key,容易误判
真正容易被忽略的是:唯一二级索引冲突从来就不是“只锁一行”,它本质是边界防护行为;想靠 INSERT IGNORE 规避锁竞争,反而可能因 S 锁堆积放大死锁概率。











