整个国庆长假自闭了,加上这段时间比较忙,所以一直没有更新,工作日一般晚上回家会看会书,然后总结一下,写写博客,然后存为草稿,一直到第二天白天才会发布出来,为什么要到第二天才发布,是因为白天会花个半小时检查一下,然后再发布
好了,回归正题,这章节讲的是mysql中的死锁
一、死锁的概念
(1)在数据库中,死锁是指两个及两个以上的事务在执行过程中,因争夺锁资源造成一种相互等待的现象。
(2)解决死锁最简单的方式是不要有等待,将所有等待的事务都进行回滚,让事务重新开始,虽然这可以解决问题,但是在生产环境中,这种操作容易影响到并发性能,严重到任何一个事务都不能进行,这样的问题比死锁的问题更加严重
(3)还有一种解决方式是超时,就是两个事务在相互等待的时候,其中一个事务设置等待时长,超过了一个阈值,则进行回滚,让另外一个事务继续执行,这种机制虽然简单,但是如果超时的事务所占的权重比较大,会出现回滚事务的时间比另外一个事务正常进行的时间还要多,这样一来就不太合适了
(4)因此,数据库有专门的方式来进行死锁检测,称为wait-for graph(等待图)的方式,innoDB也是采用的这种方式,数据库会保存以下两种信息:
锁的信息链表
事务等待链表
(5)通过以上链表可以构造一个图,能够看到图中是否存在回路,如果有回路,则说明有死锁产生,每个事务请求锁都会判断是否存在回路,若存在则说明有死锁,innoDB会选择回滚数据量最小的事务
(6)wait-for graph的死锁检测采用了深度优先的算法实现,在innoDB 1.2 版本之前,都是采用递归方式,从1.2版本开始,将递归用非递归的方式实现,从而进一步提高了性能
二,下面演示一下死锁,两个事务相互等待的过程
如果程序是串行的,不可能发生死锁,只有在并发的时候才会发生死锁
同时开启两个事务 A和B;
进行如下操作,左边A,右边B
ps: for update是在数据库中上锁用的,可以为数据库中的行上一个排它锁。当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新
接着在事务B中执行,查询id为96的数据,之前在A中查询id为95的
mysql> select * from doc_share a where a.id=96 for update;
+----+---------------------+---------+---------------------+------+-------------+--------+----------+
| id | doc_id | version | end_time | uid | read_status | ac_uid | is_share |
+----+---------------------+---------+---------------------+------+-------------+--------+----------+
| 96 | 1597023223364439740 | A0 | 2020-08-12 00:00:00 | 1376 | 1 | 3 | 1 |
+----+---------------------+---------+---------------------+------+-------------+--------+----------+
1 row in set (0.00 sec)
然后在A中执行之前B中同样的语句
我们看到A中无结果,一直在等待,随后会报错,等待超时
如果在A等待的过程中,我们在B中同样执行查询id为95的,我们一起来看看结果
由图可知;当一个事务A对id为95的数据上S锁的时候,此时开启另外一个事务B,事务B对id为96上锁,此时事务A再去对96的id进行数据更改的操作,会一直等待事务B对id为96的数据完成,这时,事务B再去对id为95的数据更改操作时,会出现报错信息为1213,即死锁信息,意思就是,A等待B释放,B也等待A释放,所以在B操作上会出现死锁,InnoDB自动帮我们判断了,进行了回滚操作,导致A最后能获取到结果