如何解决MySQL 5.7在RC级别下依然出现死锁_排查二级索引上的加锁逻辑

浅芳君_8271

浅芳君_8271

2026-05-25

473人浏览

原创

rc级别下insert on duplicate key update仍会死锁,根本原因是唯一二级索引冲突触发隐式x锁与插入意向锁的循环等待;innodb在rc下虽不加间隙锁,但为唯一键冲突检测必须对二级索引记录加record-level x锁,且加锁顺序不一致导致死锁。

如何解决mysql 5.7在rc级别下依然出现死锁_排查二级索引上的加锁逻辑

RC级别下依然死锁,根本原因不是“间隙锁失效”没生效,而是唯一二级索引冲突触发了隐式X锁 + 插入意向锁(Insert Intention Lock)的等待链。

为什么RC级别下INSERT ON DUPLICATE KEY UPDATE还会死锁

很多人误以为RC隔离级别就完全不加间隙锁,但官方文档明确说明:gap locking is used only for foreign-key constraint checking and duplicate-key checking。也就是说,只要涉及唯一键(主键或UNIQUE索引)冲突判断,InnoDB就必须加锁防止并发插入覆盖同一键值——哪怕在RC下也逃不掉。

典型死锁场景是两个事务按不同顺序尝试插入/更新同一组唯一键:

  • 事务A执行 INSERT ... ON DUPLICATE KEY UPDATE 插入 songid=16,先获取该二级索引记录上的X锁(因冲突检测需要排他访问)
  • 事务B同时执行同语句插入 songid=17,也拿到对应X锁
  • 接着A尝试插入 songid=17,发现已被B加X锁,开始等待
  • B尝试插入 songid=16,发现已被A加X锁,也等待 → 循环等待成立

注意:这个X锁不是加在聚簇索引(主键)上,而是直接加在二级唯一索引记录上(uk_column_key),因为冲突检测第一步就是查二级索引。

如何确认死锁是否源于二级索引加锁

关键看 SHOW ENGINE INNODB STATUS\G 输出中的 WAITING FOR THIS LOCK TO BE GRANTED 和 HOLDS THE LOCK(S) 部分:

MySQL
MySQL

编写正确的MySQL查询,避免字符集、索引和锁方面的常见陷阱。

下载
  • 若出现 index PRIMARY 但WHERE条件里根本没用主键(比如只用了WHERE uk_column = ?),说明优化器回表时被迫锁了主键——这是索引设计缺陷
  • 若明确看到 index uk_column_key(你的唯一二级索引名),且锁模式是 X locks rec but not gap,基本可锁定问题根源
  • 若出现 insert intention 和 gap before rec 并存,说明事务已在间隙上申请插入意向锁,但被其他事务的X锁阻塞

不要只盯着“PRIMARY”,很多死锁实际起始于二级索引——InnoDB对唯一索引的冲突检查是原子的,必须先锁二级索引记录,再决定是否回表加主键锁。

避免二级索引死锁的实操方案

核心思路是打破“不同顺序加锁”这个前提,让所有事务以相同逻辑路径访问数据:

  • 批量操作前,先用 SELECT ... FROM table WHERE uk_column IN (...) ORDER BY uk_column FOR UPDATE 预加锁,强制按索引顺序获取锁(ORDER BY 是关键)
  • 改用 REPLACE INTO?不行——它本质是 DELETE + INSERT,会引入更多锁阶段,死锁风险更高
  • 业务层做写入顺序收敛:例如所有客户端统一按 uk_column % N 分桶,每个桶内串行处理,从源头消灭并发冲突
  • 临时降级为 INSERT IGNORE + 单独 UPDATE,但要注意:这会丢失“冲突时原子更新”的语义,且两次网络往返增加延迟

最稳妥的长期解法是:确保二级唯一索引字段具备天然有序性(如时间戳+序列号组合),并让应用始终按该字段单调递增写入——这样InnoDB加锁顺序天然一致,死锁概率趋近于零。

RC下二级索引加锁最容易被忽略的点

很多人以为“RC不加gap lock”就能高枕无忧,却忽略了三个隐蔽事实:

  • INSERT ... ON DUPLICATE KEY UPDATE 在RC下仍会对**唯一二级索引记录本身加X锁**(不是gap,是record-level X lock),只为做duplicate check
  • 即使你没显式写 FOR UPDATE,任何DML都是当前读,都会触发加锁;快照读(普通SELECT)才不加锁
  • 如果唯一索引是联合索引(如 UNIQUE KEY (a,b)),而SQL只命中 a,InnoDB会升级为范围锁(gap + record),此时RC也无法避免间隙影响

真正决定锁行为的,从来不是隔离级别开关,而是SQL怎么写、索引怎么建、数据怎么分布——别信“RC就安全”的经验主义。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

3389

3

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

2023.07.25

2368

9

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

2023.08.02

1300

5

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.09

1258

4

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

2023.09.05

1376

5

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

2258

7

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

2023.09.20

3660

8

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

2023.09.22

16955

6

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

549

3

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL vs MySQL
PostgreSQL vs MySQL

共1课时 | 190人学习