文章目录
- 死锁是什么?
- 死锁的四个必要条件
- 避免死锁的策略
本篇文章是基于《MySQL45讲》来写的个人理解与感悟。
死锁是什么?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
比如:两个线程A、B各自持有一个无法共享的资源,并且他们都需要获取对方现在持有的资源才能进行下一步,但是他们又必须等对方释放了才能去获取,于是A等待B,B也在等待A。如此这般,死锁就产生了。
而对于MySQL,如果只支持到表锁的引擎,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。也就意味着如果我们对某一个表进行修改的时候这张表就会被锁住,从而影响效率。因此有了行锁。
而行锁是遵循两阶段提交协议的,这也就意味着虽然我们可以主动开启行锁,但是只有事务提交的时候行锁才会被释放。那么这就容易产生了死锁。
死锁的四个必要条件
● 互斥条件:指多个线程不能同时使用同一个资源。
● 请求与保持条件: 当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 C 持有了,所以线程 A 就会处于等待状态,但是线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源 1。
● 不可剥夺条件:当线程已经持有了资源 ,在自己使用完之前不能被其他线程获取,线程 B 如果也想使用此资源,则只能在线程 A 使用完并释放后才能获取。
● 换路等待条件:在死锁发生的时候,两个线程获取资源的顺序构成了环形链。
图片来源:小林coding
那么我们接下来看一下死锁的例子:
比方说,此时有两个用户去电影院购票,然后购票其实是有三个步骤。
1. 从顾客A、B账户余额中扣除电影票价;
2. 给影院的账户余额增加这张电影票价;
3. 记录一条交易日志。
因为要保证数据库完整性,所以会用到事务。假设此时我们两个用户都去购票,然后因为增加电影院的账户余额的这个动作两个用户要需要进行,这个时候会需要行锁去操作增加,然后又因为行锁其实是必须要在事务提交之后才会释放。
所以一旦某个操作阻塞住了,那么就会产生死锁。
而且有些电影院可以有预售秒杀的那种活动嘛,这样的话,一旦阻塞住,那么cpu会飙升,最后导致数据库挂了。
避免死锁的策略
1. 超时等待:也就是等待一定时间,如果没有获取到锁那么线程就自动退出,但是等待的时间很不好把握,所以一般用第二种方法。
2. 死锁检测:也就是说,发起死锁检测,然后查看是否发生死锁【是否出现循环等待】,一旦发生就会回滚这个死锁链条的某个事务,这样的话就让其他事务可以继续执行。
但是死锁检测也不是万能的,比如还是出现了上面一样的情况【对同一行记录进行更新】,假设同时有一千条并发线程同时去操作,那么死锁检测是O(N)的,所以就回去检查完1000个线程之后才检测完,而在这个期间会占用大量的CPU资源的,所以就会看到虽然CPU占用资源很高,但是处理的效率缺差强人意。
那么如何解决这种热点行更新操作导致的问题呢?
1. 抓住矛盾的主要方面,也就是说如果你可以确定这个业务不会发生死锁问题,就可以把死锁检测关掉。
但是这种操作本身带有一定的风险,因为业务设计的时候一般不会把死锁当做一个严重错误,毕竟出现死锁了,就回滚,然后通过业务重试一般就没问题了,这是业务无损的。而关掉死锁检测意味着可能会出现大量的超时,这是业务有损的。
2. 控制并发度