一、死锁是怎么产生的?
两个或多个事务在操作过程中,产生了多个锁,这些事务相互等待着对方释放锁,形成闭环阻塞,导致死锁。
举个例子
假如有如下订单记录表,只有这几条记录,其中 id 字段为主键索引,order_no 字段普通索引,也就是非唯一索引:
假设这时有两个事务,一个事务要插入订单 1007 ,另外一个事务要插入订单 1008,因为需要对订单做幂等性校验,所以两个事务先要查询该订单是否存在,不存在才插入记录,过程如下:
可以看到,两个事务都陷入了等待状态(前提没有打开死锁检测),也就是发生了死锁,因为都在相互等待对方释放锁。
具体是怎么发生的呢?
首先,因为两条记录都不存在,事务A的当前读会产生一个间隙锁,然后事务B的当前读也产生了一个间隙锁(间隙锁是相互兼容的,可以同时存在),接着事务A想要插入一条记录,但是发现B事务加上了间隙锁,不能插入,于是产生一个插入意向锁,事务B也想要插入一条记录,但是发现A的间隙锁,于是也产生一个插入意向锁。就这样,两个事务互相等待对方释放间隙锁,从而形成死锁,阻塞线程。
如何解除死锁?
在数据库层面,有两种策略通过「打破循环等待条件」来解除死锁状态:
1、设置事务等待锁的超时时间。
当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以继续执行了。在 InnoDB 中,参数 innodb_1ock_wait_timeout 是用来设置超时时间的,默认值时 50 秒。
当发生超时后,就出现下面这个提示:
ERROR 1205(HY000): Lock wait timeout exceeded; try restarting transaction
2、开启主动死锁检测。
主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将innodb_deadlock_detect参数设置为 on,表示开启这个逻辑,默认就开启。
当检测到死锁后,就会出现下面这个提示:
ERROR 1213 (48001): Deadlock found when trying to get lock; try restarting transaction