MySQL知识点回顾:锁问题(三)死锁

本文详细探讨了InnoDB数据库中死锁的两种常见原因:DuplicateKeyError引发的事务冲突和GAP与InsertIntention冲突。通过实例分析,解释了事务加锁行为、死锁条件和避免死锁的方法,揭示了锁兼容矩阵在事务并发中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

锁的兼容矩阵如下:

排它锁(X)意向排它锁(IX)共享锁(S)意向共享锁(IS)
排它锁(X)NNNN
意向排它锁(IX)NOKNOK
共享锁(S)NNOKOK
意向共享锁(IS)NOKOKOK

首先执行以下示例数据库的语句:

mysql> CREATE TABLE test (
id int(11) NOT NULL,
code int(11) NOT NULL, 
PRIMARY KEY(id), 
KEY (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 

mysql> INSERT INTO test(id,code) values(1,1),(10,10);

一、 Duplicate key error引发的死锁

此例子默认隔离级别为RR
然后按照以下顺序执行SQL

时间会话A会话B会话C
1BEGIN;
2insert into test values(2,2);
3BEGIN;
4insert into test values(2,2);
5BEGIN;
6insert into test values(2,2);
7rollback;

在这里插入图片描述
当事务一执行回滚时,事务二和事务三发生了死锁。InnoDB的死锁检测一旦检测到死锁发生,会自动失败其中一个事务,因此看到的结果是一个失败另一个成功。

1、为什么会造成死锁?
  死锁产生的原因是事务一插入记录时,对(2,2)记录加X锁,此时事务二和事务三插入数据时检测到了重复键错误,此时事务二和事务三要在这条索引记录上设置S锁,由于X锁的存在,S锁的获取被阻塞。
  事务一回滚,由于S锁和S锁是可以兼容的,因此事务二和事务三都获得了这条记录的S锁,此时其中一个事务希望插入,则该事务期望在这条记录上加上X锁,然而另一个事务持有S锁,S锁和X锁互相是不兼容的,两个事务就开始互相等待对方的锁释放,造成了死锁。

2、事务二和事务三为什么会加S锁?而不是直接等待X锁
事务一的insert语句加的是隐式锁(隐式的Record锁、X锁),但是其他事务插入同一行记录时,出现了唯一键冲突,事务一的隐式锁升级为显示锁。
事务二和事务三在插入之前判断到了唯一键冲突,是因为插入前的重复索引检查,这次检查必须进行一次当前读,于是非唯一索引就会被加上S模式的next-key锁,唯一索引就被加上了S模式的Record锁。

2、GAP与Insert Intention冲突引发死锁

此例子默认隔离级别为RR
然后按照以下顺序执行SQL

时间会话A会话B
1BEGIN;
2BEGIN;
3select * from test where code=5 for update;
4select * from test where code=10 for update;
5insert into test values(7,7);
6insert into test values(7,7);
7Query OK, 1 row affected (5.03 sec)
8ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

在这里插入图片描述
一、回顾select…for update的加锁范围
  首先回顾一下两个事务中的select … for update做了哪些加锁操作。

  code=5时,首先会获取code=5的索引记录锁(Record锁),根据之前gap锁的介绍,会在前一个索引和当前索引之间的间隙加锁,于是区间(1,5)之间被加上了X模式的gap锁。除此之外RR模式下,还会加next-key锁,于是区间(5,10]被加了next-key锁;

  因此,code=5的加锁范围是,区间(1,5)的gap锁,{5}索引Record锁,(5,10]的next-key锁。即区间(1,10)上都被加上了X模式的锁。
  同理,code=10的加锁范围是,区间(5,10)的gap锁,{10}索引Record锁,(10,+∞)的next-key锁。
  由gap锁的特性,兼容矩阵中冲突的锁也可以被不同的事务同时加在一个间隙上。上述两个select … for update语句出现了间隙锁的交集,code=5的next-key锁和code=10的gap锁有重叠的区域——(5,10)。

二、死锁的原因
  当事务一执行插入语句时,会先加X模式的插入意向锁,即兼容矩阵中的IX锁。
  但是由于插入意向锁要锁定的位置存在X模式的gap锁。兼容矩阵中IX和X锁是不兼容的,因此事务一的IX锁会等待事务二的gap锁释放。

  事务二也执行插入语句,与事务一同样,事务二的插入意向锁IX锁会等待事务一的gap锁释放。

  两个事务互相等待对方先释放锁,因此出现死锁。

三、总结

  除了以上给出的几种死锁模式,还有很多其他死锁的场景。
  无论是哪种场景,万变不离其宗,都是由于某个区间上或者某一个记录上可以同时持有锁,例如不同事务在同一个间隙gap上的锁不冲突;不同事务中,S锁可以阻塞X锁的获取,但是不会阻塞另一个事务获取该S锁。这样才会出现两个事务同时持有锁,并互相等待,最终导致死锁。

参考文献:InnoDB的锁机制浅析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值