并发场景下的死锁原因及规避解决方法

1,814 阅读5分钟

一、处理解决死锁的思路

处理思路: 预防 → 避免 → 检测 → 解除。

如果能预防是最好, 其次是尽可能的避免,最后如果检测到死锁, 那就想办法尽快的解除它。

二、产生死锁的四个要素

  1. 互斥条件:一个资源每次只能被一个线程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

只要破坏死锁 4 个必要条件之一中的任何一个,死锁问题就能被解决。 但实际场景中, 我们不能破坏互斥性,要解决死锁问题,就需要破坏其他三个条件。

三、 死锁的检测算法:

file

这里有三个进程四个资源,每个数据代表的含义如下:

  • E 向量:资源总量
  • A 向量:资源剩余量
  • C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
  • R 矩阵:每个进程请求的资源数量

进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P2 可以执行,执行后释放 P2 拥有的资源,A = (4 2 2 1) 。P1 也可以执行。所有进程都可以顺利执行,没有死锁。

死锁检测算法总结如下:

每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。

  1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。

  2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。

  3. 如果没有这样一个进程,算法终止。

四、 MySQL的死锁问题原因

  1. InnoDB 隔离级别、索引和锁的关系 假设一张消息表(msg),里面有3个字段。id是主键,token是非唯一索引,message没有索引。 Innodb对于主键使用了聚簇索引,这是一种数据存储方式,表数据是和主键一起存储,主键索引的叶结点存储行数据。对于普通索引,其叶子节点存储的是主键值。 file

  2. 索引与锁的关系

    1)ID操作, SQL语句: delete from msg where id=2; 由于根据ID删除, 产生行锁, 锁住单条记录。 file

    2)索引操作,SQL语句:delete from msg where token=’cvs'; 由于token是二级索引, 会锁住二级索引所对应的记录,共两行。 file

    1. 全表扫描,SQL语句: delete from msg where message=‘订单号’; file
  3. 幻读与间隙锁关系 事务A在第一次查询时得到1条记录,在第二次执行相同查询时却得到两条记录。从事务A角度上看是见鬼了! 其实这就是幻读,尽管加了行锁,但还是避免不了幻读。 file InnoDB默认隔离级别是可重复读(Repeatable read(RR)), 所谓可重复读是指: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 如何解决幻读,就需要采用gap间隙锁。

    比如事务A查询完数据后, 执行update更新: update msg set message=‘订单’ where token=‘asd’; 因为token是二级索引, 除了在索引的记录上添加X锁,此外,还在’asd’与相邻两个索引的区间加上锁。 file 当事务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