Innodb 死锁介绍及案例详解

2,877 阅读5分钟

死锁

当两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,就称之为死锁。

死锁问题是计算机系统中常见的问题,在Innodb中同样存在。

死锁的产生条件

死锁产生必须要满足以下四个条件:

  1. 互斥条件: 即为某个资源在同一时间只允许被一个单元占有。
  2. 不可抢占条件:被单元占有的资源不可被其它单元抢占。
  3. 占有且申请条件:单元当前至少占有一个资源,且该单元同时向系统申请其它的资源。
  4. 循环等待条件:单元之前存在一个资源的循环等待序列。

死锁范例

死锁

在百度上盗了一张图,如图所示,单元T1和T2各自占有了一个资源,又同时想要占据别人的资源,这样僵持不下,也就产生了死锁。对于计算机系统而言,死锁会导致系统停滞,对于Innodb而言,死锁会给数据的读写产生阻碍。

Innodb中的死锁

引言

在介绍之前,先讲一个我们工作中踩到的一个坑。

背景描述

我们的订单表中存在一个Unique Key,假设该Unique Key的名字为U_KEY,它是由一个ID和时间戳构成的。在实际运行中,存在用同一个Unique Key反复创建订单的行为,这种情况的发生有可能是因为恶意刷单或者是偶然的请求重发。对于这种情况,先到达的插入请求会成功,之后的请求会产生Unique Key冲突而失败,但这个过程中会时不时的出现Dead Lock Detected的情况(数据库会自动Kill死锁),这里我们采用的是insert ignore

问题原因

在插入时,MySQL会给行记录加上排他锁(Index-record lock),假如此时有三个同样的插入请求,都开启了事务,其中一个先拿到了排他锁开始插入,之后的事务会出现Duplicate Key错误,而此时它们会申请该行的共享锁,如果这个时候拿到排他锁的事务回滚,那么另外两个事务会同时申请该行的排他锁(过程参考MySQL锁机制。由于排他锁和共享锁是互斥的,此时就产生了死锁的情况。

这里可能会有人有疑问,为什么出现Duplicate Key错误的时候会加共享锁了,我的理解是冲突检测本身是一种读操作,所以冲突之后的轮询需要加共享锁。据这个例子的主要目的是告诉大家死锁问题是我们工作中会遇到的,需要重视。

Innodb死锁产生条件

  1. 两个以上的并发事务
  2. 每个事务当前持有了锁,且未释放
  3. 每个事务都在申请新的锁
  4. 事务之间产生了锁资源的循环等待

其实这也就是Innodb版的死锁条件,只是资源变成了锁而已。要避免死锁,其实就是要避免上诉死锁条件的产生,这种坑其实总会踩到了才会重视起来。

Innodb死锁范例

明白了死锁的产生条件,那么我试着来实际测试一些典型的死锁情况。

1. 典型的死锁案例

表的开始状态如下图

初始状态

开启事务A,更新id=5的记录 A 5

开启事务B,更新id=6的记录 B 5

开启事务A,更新id=6的记录,会发现卡住了 A 6

再在事务B,更新id=5的记录,会发现出现了死锁 B 5

然后事务A更新id=6的记录执行成功,因为事务B因为死锁被数据库Kill掉了

A commit

2. Insert死锁

在引言中我介绍了一个我们工作中遇到的坑,下面我们尝试自己复现一下。

表的开始状态如下图

初始状态

事务A插入一条id=7的记录

A insert

事务B执行同样的插入语句

Other insert

会发现事务卡住了

事务C执行同样的插入语句

Other insert

发现事务同样卡住了

事务A rollback

A rollback

事务A rollback后,会发现事务C爆出了 死锁 ,符合我们的设想

C deadlock

需要注意的是 ,如果事务A不是rollback,而是commit,那么不会产生死锁,而是爆出两个Duplicate Error,读者可以想一下这是为什么?

3. 工作中的另一个死锁Case

这个Case产生的原因是,事务想要insert一条记录,然后select for update该条记录,但如果针对一条记录,有三个事务并发执行,那么同样会产生死锁,死锁的原因其实和上面类似,都是因为在产生Duplicate Error时,事务会加Share lock。

表的开始状态如下图

初始状态

事务A如下图执行

Case a

事务B和C都执行语句如下 事务B Case b c 事务C Case b c 会发现事务都卡住

事务A此时commit

Case a commit

会发现事务B和C同时爆出Duplicate Key Error

Case b c Duplicate

事务B和C执行Select for update

这时事务B和C依次也执行select for update,事务B会卡住,事务C会产生如下图的死锁。

Case b c deadlcok