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可能会锁定更大的范围甚至退化成表锁,因此为查询列建立合适的索引也十分重要。

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

相关推荐
●VON6 小时前
鸿蒙Flutter实战:分类管理页BottomSheet CRUD
数据库·flutter·华为·harmonyos·鸿蒙
Cosolar6 小时前
Chroma向量库面试学习指南
数据库·人工智能·面试·职场和发展·数据库架构
企服AI产品测评局7 小时前
Agent适配信创环境实测:企业级自动化如何实现国产操作系统与数据库全兼容?
运维·数据库·人工智能·ai·chatgpt·自动化
cfm_29147 小时前
Redis数据安全性解析
数据库·redis·缓存
DIY源码阁8 小时前
JavaSwing学生成绩管理系统 - MySQL版
java·数据库·mysql·eclipse
NiceCloud喜云9 小时前
Claude Code Routines 实战:三种触发器跑通云端自动化编码
android·运维·数据库·人工智能·自动化·json·飞书
辞忧九千七9 小时前
Redis 单机一主二从主从复制完整搭建指南
数据库·redis·缓存
lzhdim9 小时前
SQL 入门 16:SQL 事务隔离级别与死锁解析(易懂)
数据库·sql
AI 小老六10 小时前
Claude Code 如何压缩上下文:Microcompact、Prompt Cache 与 cache_edits 工程拆解
数据库·人工智能·ai·语言模型·架构·系统架构
Chasing__Dreams10 小时前
Redis--基础知识点--32--redis底层存储结构
数据库·redis·缓存