死锁的必要条件
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
以上解释稍显苦涩,翻译一下就是:
必须存在多个并发进程(事务)
每个事务都持有了锁
每个事务还需要继续持有其它锁
每个事务还需持有的锁恰是其它事务占有的锁,并且一直循环等待。
一般死锁
每个事务执行两条SQL,分别持有了一把锁,然后加另一把锁,产生死锁。
多数情况死锁示例
事务1 已经持有了order_id = 1 的X锁 ,试图获得order_id= 5的X锁,但此锁已被事务2 占有 , 同时事务2 已经持有了order_id = 5 的X锁 ,试图获得order_id= 1的X锁,但此锁已被事务1 占有了。 因此进入了一种互等状态,死锁产生。
回滚导致死锁
回滚导致死锁示例
order_id是唯一索引, 事务1, 事务2 , 事务3 都执行相同的操作,但事务1 后来执行了回滚操作, 事务2 和事务3 造成死锁。
当事务1中插入一条记录的时候,这个会持有插入记录的行锁即X锁,当其它事务2, 事务3 再次插入相同的记录的时候,就会申请S锁(首先执行冲突检查),即事务2 和事务3 都申请到了S锁, 事务1 回滚以后,释放资源,这时事务2 和事务3 都持有了S锁,都申请加X锁,此时产生死锁。
间隙锁导致死锁
order_id 是唯一索引,但非主键, 先查看表中内容 select order_id from mtp_order ; 即表中仅存在 order_id= 1, 5, 9 这样三条记录。
order_id |
---|
1 |
5 |
9 |
间隙锁导致死锁
order_id是唯一索引, 事务1, 事务2分别先加gap锁, 事务1执行插入操作,但事务2 已在1,5之间加gap锁,所以被阻塞,当事务2执行插入时,因为事务1中在1,5之间加gap锁,因此产生死锁。
隐式锁(锁膨胀)死锁
锁膨胀导致死锁
事务1 和事务2 并发执行相同的sql语句, 发生死锁。 事务1中先执行后面的子select语句, 加隐式S锁, 这时事务2中也开始执行后面的select语句, 因事务1中已经加过隐式锁,所以事务2中此时加的是显示S锁,相当于执行了锁升级(锁膨胀),此时事务1再执行的时候就会申请X锁, 因为事务2中已经存在S锁,因此申请未成功,同样事务2也不能执行,造成死锁。 具体可通过 show engine innodb status 查看事务状态。 以及到表中innodb_lock_waits和innodb_locks中进行锁状态查询。
索引merge导致死锁
什么是索引merge: 操作时,引擎层面同时走了两条索引(时间上可能有先后),将索引结果进行merge操作,然后返回给Server层 。
示例: (refund_number 和user_id 分别为二级非唯一索引) ,且区分度差不多,如下图所示。
索引merge示例
索引merge导致死锁
避免死锁的技巧
1: 以固定的顺序访问表和行
2: 大事务拆小事务,大事务更加倾向于死锁。
3: 为表添加合理的索引
4: 降低事务隔离级别,如RR降到RC
5: 尽量一次申请到所有的资源。
6: 更新或者删除操作之前都确定记录是存在的,然后再做操作。
死锁检测: 等待图(属于有向图)。