二级索引更新不直接导致死锁但会放大冲突概率,因非唯一索引引入临键锁且加锁顺序与主键无关;唯一索引虽无间隙锁,但多列联合或null值仍可能引发死锁。

二级索引更新本身不直接“产生”死锁,但会放大锁冲突概率——根本原因是:InnoDB 对二级索引的加锁顺序与主键顺序无关,且非唯一索引必然引入间隙锁,导致多个事务在相同索引区间内交叉加锁。
非唯一二级索引的等值查询必然触发临键锁
当 SQL 使用 WHERE col = ? 查询或更新一个非唯一二级索引列(如 status、category_id)时,InnoDB 在 RR 隔离级别下不会只锁匹配行,而是加 Next-Key Lock:即「该值对应的所有行记录锁 + 该值所在索引间隙的间隙锁」。
常见错误现象:
- 两个事务同时执行
UPDATE t SET x=1 WHERE status = 0,却因间隙重叠而互相等待 - 执行计划显示走了
index_status,但EXPLAIN的key_len很小,说明只用了索引前缀,实际锁范围远超预期
实操建议:
- 确认该二级索引是否真的需要支持等值更新 —— 若业务上
status只有 3–5 个取值,考虑用枚举+覆盖索引替代 - 用
SELECT * FROM t WHERE status = 0 FOR UPDATE+SHOW ENGINE INNODB STATUS观察实际锁住的heap no和gap范围 - 若必须用,优先将该列设为
UNIQUE索引(此时退化为纯行锁,无间隙锁)
二级索引更新需回表,锁资源叠加两次
二级索引叶子节点只存主键值,所以任何通过二级索引定位的 UPDATE 都要先锁二级索引项,再根据主键去聚簇索引上加锁 —— 这是两阶段加锁,且顺序固定:先二级索引,再主键索引。
使用场景:
- 事务 A 更新
WHERE user_id = 123 AND status = 0,命中idx_user_status→ 锁idx_user_status上的记录 + 主键上的对应行 - 事务 B 更新
WHERE status = 0 AND created_at > '2026-04-01',走idx_status→ 锁整个status=0区间 + 所有匹配主键行
问题就出在这里:事务 A 锁了主键某几行,事务 B 却因间隙锁锁了更大范围的主键区间;而事务 B 同时又锁了 idx_status 上的一段,事务 A 恰好也要更新同区间其他行 —— 锁资源交叉,顺序又不同,死锁自然发生。
实操建议:
- 避免在二级索引上做范围条件更新(如
status IN (0,1)),改用主键批量更新 + 应用层分片 - 检查
UPDATE语句是否隐式触发回表:如果SET子句里修改了二级索引列本身(如UPDATE t SET status = 1 WHERE status = 0),InnoDB 会先删旧索引项、再插新索引项,锁行为更复杂 - 用
INFORMATION_SCHEMA.INNODB_TRX+INNODB_LOCK_WAITS查看死锁时两个事务各自持有的锁对象(LOCK_TRX_ID和LOCK_INDEX)
唯一二级索引更新仍可能死锁,但原因不同
唯一索引(含主键)的等值查询只会加记录锁(Record Lock),不加间隙锁 —— 这点常被误认为“绝对安全”。但只要涉及多列联合唯一索引,或更新语句命中多个唯一索引,死锁风险仍在。
典型例子:
-
UNIQUE KEY idx_pool_id (pool_id, identifier),两个事务分别执行:UPDATE t SET x=1 WHERE pool_id=100 AND identifier IS NULLUPDATE t SET x=2 WHERE pool_id=100 AND identifier = 'abc' - 由于
IS NULL在 B+ 树中排在最左,而'abc'是具体值,二者在索引树上物理位置相邻甚至重叠,InnoDB 仍可能对同一页面加锁冲突
性能影响:
- 唯一索引更新比非唯一索引快,但并发写入吞吐仍受限于聚簇索引页争用(热点主键页分裂)
- 若唯一索引列允许
NULL,MySQL 会把所有NULL值视作相等并聚集在一起,极易形成锁热点
实操建议:
- 联合唯一索引中,把高区分度列放在前面(如
(identifier, pool_id)而非反序) - 避免在唯一索引列上用
IS NULL或IS NOT NULL条件更新,改用默认值(如''或0)代替NULL - 压测时重点关注
Innodb_row_lock_waits和Innodb_row_lock_time_avg,而非仅看死锁数
真正难处理的不是“哪里加了锁”,而是“哪些锁会被其他事务无意间共享”。二级索引的临键锁边界模糊、回表路径不可见、NULL 值特殊排序,这三者叠加,让死锁复现变得高度依赖数据分布和执行时机 —— 所以线上排查必须结合 SHOW ENGINE INNODB STATUS 中的 *** (1) WAITING FOR THIS LOCK TO BE GRANTED 和 *** (2) HOLDS THE LOCK(S) 逐行比对索引页号与 heap no。











