Mysql--基础知识点--99--两个线程同时给同一个间隙加锁 造成死锁的原因

mysql 在REPEATEABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT...ROR UPDATE加排他锁,在没有符合该记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可以避免问题。

解释以上发生死锁的原因解决死锁的原理

1 死锁的原因

InnoDB间隙锁(Gap Lock)的一个核心特性:间隙锁之间是不互斥的,允许多个事务在同一间隙上持有锁

你描述的完整流程,会形成一个"双方各自持有了对方需要的间隙锁,然后又试图获取对方的间隙锁"的经典死锁循环。我们分步拆解一下。

🔍 问题根源:RR隔离级别下的间隙锁与死锁

REPEATABLE-READ(RR)隔离级别下,为了彻底防止幻读,InnoDB引入了间隙锁机制。当 SELECT ... FOR UPDATE 查询一条不存在的记录时,它不会空手而归,而是会锁定这条记录"本应存在"的区间。

  1. 第一步:查询并加间隙锁

    • 事务T1执行SELECT ... WHERE id = 5 FOR UPDATE,假设id=5的记录不存在。T1会在id=5所属的间隙(例如(3, 8)区间)上加上间隙锁(Gap Lock)
    • 事务T2执行相同的查询。因为间隙锁可以共存 ,它不会阻塞T2,T2也会成功地在同一个间隙(3, 8)上加上间隙锁。
  2. 第二步:尝试插入并死锁

    • 现在T1尝试INSERT INTO ... VALUES (5)。在插入前,它需要申请一个与间隙锁互斥的插入意向锁(Insert Intention Lock)
    • 由于间隙(3,8)上已有T2的间隙锁,T1的插入意向锁请求会被阻塞,T1进入等待T2的状态。
    • 同样,T2尝试插入时,其插入意向锁也会被T1的间隙锁阻塞,T2进入等待T1的状态。
    • 至此,死锁(Deadlock) 形成,系统将回滚其中一个事务以解除僵局。

这个经典死锁场景在MySQL官方Bug库中也被多次记录和讨论。

2 为什么 READ-COMMITTED (RC) 能解决问题?

将隔离级别降为 READ COMMITTED 能从根本上避免此问题,因为它完全改变了加锁策略:

  1. 禁用间隙锁 :在RC隔离级别下,InnoDB不使用间隙锁来防止幻读(除了极少数涉及唯一索引检查的特殊情况)。
  2. 仅锁定存在的行SELECT ... FOR UPDATE 只会对实际存在 并符合条件的记录加上行锁(Record Lock)。对于不存在的记录,它不会锁定任何间隙。

因此,在你描述的场景中,T1和T2的SELECT ... FOR UPDATE都不会获得任何锁,更不会产生间隙锁的冲突,死锁的循环链条就从源头上被打破了。

💎 总结:死锁如何形成?

下表清晰对比了两种隔离级别下的行为差异:

隔离级别 SELECT ... FOR UPDATE (记录不存在时) 能否避免死锁 原因
REPEATABLE READ (RR) 间隙锁 (Gap Lock) 间隙锁不互斥,允许多个事务同时持有,但它们都与插入意向锁互斥,从而形成等待环路导致死锁。
READ COMMITTED (RC) 不加锁 仅对存在的行加记录锁,从根本上杜绝了针对不存在记录的间隙锁冲突。

对于这种SELECT ... FOR UPDATE后再INSERT的业务模式(通常称为get_or_create),一个更推荐的解决方案是直接使用INSERT ... ON DUPLICATE KEY UPDATE语句,这通常能在一条SQL内原子化地完成操作,既保证了并发安全,也无需在应用层编写复杂的加锁逻辑。

另外,由于间隙锁是针对索引记录的,如果你查询的列上没有建立索引,SELECT ... FOR UPDATE可能会锁定更大的范围甚至退化成表锁,因此为查询列建立合适的索引也十分重要。

你遇到的这个问题在并发编程中确实很典型,要复现它需要一个完美的时机。如果想亲自体验一下,我也可以为你提供具体的代码示例和验证步骤,帮你更直观地理解这个过程~

相关推荐
2301_764150562 小时前
Pandas GroupBy:将分组数据聚合为列表并赋值到新列
jvm·数据库·python
NotFound4862 小时前
c++ 逆向工程ida pro c++如何使用ida pro插件和脚本
jvm·数据库·python
qq_189807032 小时前
CSS如何根据浏览器支持引入样式_利用@supports进行条件加载
jvm·数据库·python
qq_334563552 小时前
CSS如何实现移动端文字转阴影效果_通过text-stroke模拟描边
jvm·数据库·python
北有树2 小时前
Mysql专题面试题总结
数据库·mysql
数厘2 小时前
2.18 sql排序查询(ORDER BY、ASC、DESC)
数据库·sql·oracle
m0_377618232 小时前
mysql数据库如何通过调整读取策略提升性能_开启innodb_read_ahead
jvm·数据库·python
2301_814809862 小时前
MongoDB开启认证后应用程序出现断连假死现象
jvm·数据库·python
m0_678485452 小时前
mysql如何对比备份数据与线上数据_编写自动化校验脚本
jvm·数据库·python