一、解决解决死锁的思路
解决思路:预防 → 防止 → 检测 → 解除。
如果能预防是最好,其次是尽可能的防止,最初如果检测到死锁,那就想方法尽快的解除它。
二、产生死锁的四个因素
- 互斥条件:一个资源每次只能被一个线程应用。
- 申请与放弃条件:一个过程因申请资源而阻塞时,对已取得的资源放弃不放。
- 不剥夺条件:过程已取得的资源,在未应用完之前,不能强行剥夺。
- 循环期待条件:若干过程之间造成一种头尾相接的循环期待资源关系。
只有毁坏死锁 4 个必要条件之一中的任何一个,死锁问题就能被解决。但理论场景中,咱们不能毁坏互斥性,要解决死锁问题,就须要毁坏其余三个条件。
三、死锁的检测算法:
这里有三个过程四个资源,每个数据代表的含意如下:
- E 向量:资源总量
- A 向量:资源残余量
- C 矩阵:每个过程所领有的资源数量,每一行都代表一个过程领有资源的数量
- R 矩阵:每个过程申请的资源数量
过程 P1 和 P2 所申请的资源都得不到满足,只有过程 P3 能够,让 P3 执行,之后开释 P3 领有的资源,此时 A = (2 2 2 0)。P2 能够执行,执行后开释 P2 领有的资源,A = (4 2 2 1)。P1 也能够执行。所有过程都能够顺利执行,没有死锁。
死锁检测算法总结如下:
每个过程最开始时都不被标记,执行过程有可能被标记。当算法完结时,任何没有被标记的过程都是死锁过程。
- 寻找一个没有标记的过程 Pi,它所申请的资源小于等于 A。
- 如果找到了这样一个过程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该过程,并转回 1。
- 如果没有这样一个过程,算法终止。
四、MySQL 的死锁问题起因
- InnoDB 隔离级别、索引和锁的关系
假如一张音讯表(msg),外面有 3 个字段。id 是主键,token 是非惟一索引,message 没有索引。
Innodb 对于主键应用了聚簇索引,这是一种数据存储形式,表数据是和主键一起存储,主键索引的叶结点存储行数据。对于一般索引,其叶子节点存储的是主键值。 -
索引与锁的关系
1)ID 操作,SQL 语句:delete from msg where id=2;
因为依据 ID 删除,产生行锁,锁住单条记录。2)索引操作,SQL 语句:delete from msg where token=’cvs’;
因为 token 是二级索引,会锁住二级索引所对应的记录,共两行。3) 全表扫描,SQL 语句:delete from msg where message=‘订单号’;
-
幻读与间隙锁关系
事务 A 在第一次查问时失去 1 条记录,在第二次执行雷同查问时却失去两条记录。从事务 A 角度上看是见鬼了!其实这就是幻读,只管加了行锁,但还是防止不了幻读。
InnoDB 默认隔离级别是可反复读(Repeatable read(RR)),所谓可反复读是指:对同一字段的屡次读取后果都是统一的,除非数据是被自身事务本人所批改,能够阻止脏读和不可反复读,但幻读仍有可能产生。
如何解决幻读,就须要采纳 gap 间隙锁。比方事务 A 查问完数据后,执行 update 更新:update msg set message=‘订单’where token=‘asd’;
因为 token 是二级索引,除了在索引的记录上增加 X 锁,此外,还在’asd’与相邻两个索引的区间加上锁。
当事务 B 在新增记录,执行 insert into msg values (null,‘asd’,’hello’); commit; 时,会首先查看这个区间是否被锁上,此时 asd 曾经被锁上,则不能立刻执行,须要期待该 gap 锁被开释。这样就能防止幻读问题。
五、如何尽量躲避死锁
在理论的利用场景中,不能百分百的防止死锁,只能遵循标准尽量的缩小死锁的产生:
- 采纳固定的程序去拜访表和行数据。比方两个 job 批量更新的场景,简略办法是对 id 列表先排序,后执行,这样就防止了穿插期待锁的情景;另外,将两个事务的 sql 程序调整为统一,也能防止死锁。
- 将大事务拆分成小事务。大事务更偏向于死锁,如果业务容许,尽量将大事务拆成小事务,缩小一个事务大批量的数据更新操作。
- 在同一个事务操作中,尽可能做到一次锁定所须要的所有资源,不要被其余事物抢占,缩小死锁概率。
- 尽可能的走主键 ID 或索引进行更新。能够看到如果不走索引或 ID,将会进行全表扫描,会对每一行记录增加上锁,很容易造成死锁。
- 升高事物的隔离级别。此办法影响面比拟广,如果业务容许,将隔离级别调低也是较好的抉择,比方将魔力隔离级别从 RR 调整为 RC,能够防止掉很多因为 gap 锁造成的死锁。
本文由 mirson 创作分享,如需进一步交换,请加 QQ 群:19310171 或拜访 www.softart.cn