怎么理解MySQL wwwhj8828coml8o88O49999死锁问题

54次阅读
没有评论

共计 3104 个字符,预计需要花费 8 分钟才能阅读完成。

本篇内容介绍了“怎么理解 MySQL wwwhj8828coml8o88O49999 死锁问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1 应用日志

1 死锁是怎么被发现的?

1.1 死锁成因 检测方法

我们 mysql 用的存储引擎是 innodb,从日志来看,innodb 主动探知到死锁,并回滚了某一苦苦等待的事务。问题来了,innodb 是怎么探知死锁的?直观方法是在两个事务相互等待时,当一个等待时间超过设置的某一阀值时,对其中一个事务进行回滚,另一个事务就能继续执行。这种方法简单有效,在 innodb 中,参数 innodb_lock_wait_timeout 用来设置超时时间。

仅用上述方法来检测死锁太过被动,innodb 还提供了 wait-for graph 算法来主动进行死锁检测,每当加锁请求无法立即满足需要并进入等待时,wait-for graph 算法都会被触发。

1.2 wait-for graph 原理

我们怎么知道上图中四辆车是死锁的?他们相互等待对方的资源,而且形成环路!我们将每辆车看为一个节点,当节点 1 需要等待节点 2 的资源时,就生成一条有向边指向节点 2,最后形成一个有向图。我们只要检测这个有向图是否出现环路即可,出现环路就是死锁!这就是 wait-for graph 算法。

innodb 将各个事务看为一个个节点,资源就是各个事务占用的锁,当事务 1 需要等待事务 2 的锁时,就生成一条有向边从 1 指向 2,最后行成一个有向图。

1.2 innodb 隔离级别、索引与锁

死锁检测是死锁发生时 innodb 给我们的救命稻草,我们需要它,但我们更需要的是避免死锁发生的能力,如何尽可能避免?这需要了解 innodb 中的锁。

1.2.1 锁与索引的关系

假设我们有一张消息表(msg),里面有 3 个字段。假设 id 是主键,token 是非唯一索引,message 没有索引。

id: biginttoken: varchar(30)message: varchar(4096)

innodb 对于主键使用了聚簇索引,这是一种数据存储方式,表数据是和主键一起存储,主键索引的叶结点存储行数据。对于普通索引,其叶子节点存储的是主键值。

下面分析下索引和锁的关系。

1)delete from msg where id=2;

由于 id 是主键,因此直接锁住整行记录即可。

2)delete from msg where token=’cvs’;

由于 token 是二级索引,因此首先锁住二级索引(两行),接着会锁住相应主键所对应的记录;

3)delete from msg where message= 订单号是多少’;

message 没有索引,所以走的是全表扫描过滤。这时表上的各个记录都将添加上 X 锁。

1.2.2 锁与隔离级别的关系

大学数据库原理都学过,为了保证并发操作数据的正确性,数据库都会有事务隔离级别的概念:1)未提交读(Read uncommitted);2)已提交读(Read committed(RC));3)可重复读(Repeatable read(RR));4)可串行化(Serializable)。我们较常使用的是 RC 和 RR。

提交读 (RC):只能读取到已经提交的数据。

可重复读 (RR):在同一个事务内的查询都是事务开始时刻一致的,InnoDB 默认级别。

我们在 1.2.1 节谈论的其实是 RC 隔离级别下的锁,它可以防止不同事务版本的数据修改提交时造成数据冲突的情况,但当别的事务插入数据时可能会出现问题。

如下图所示,事务 A 在第一次查询时得到 1 条记录,在第二次执行相同查询时却得到两条记录。从事务 A 角度上看是见鬼了!这就是幻读,RC 级别下尽管加了行锁,但还是避免不了幻读。

innodb 的 RR 隔离级别可以避免幻读发生,怎么实现?当然需要借助于锁了!

为了解决幻读问题,innodb 引入了 gap 锁。

在事务 A 执行:update msg set message=‘订单’where token=‘asd’;

innodb 首先会和 RC 级别一样,给索引上的记录添加上 X 锁,此外,还在非唯一索引’asd’与相邻两个索引的区间加上锁。

这样,当事务 B 在执行 insert into msg values (null,‘asd’,’hello’); commit; 时,会首先检查这个区间是否被锁上,如果被锁上,则不能立即执行,需要等待该 gap 锁被释放。这样就能避免幻读问题。

3 死锁成因

了解了 innodb 锁的基本原理后,下面分析下死锁的成因。如前面所说,死锁一般是事务相互等待对方资源,最后形成环路造成的。下面简单讲下造成相互等待最后形成环路的例子。

3.1 不同表相同记录行锁冲突

这种情况很好理解,事务 A 和事务 B 操作两张表,但出现循环等待锁情况。

3.2 相同表记录行锁冲突

这种情况比较常见,之前遇到两个 job 在执行数据批量更新时,jobA 处理的的 id 列表为 [1,2,3,4],而 job 处理的 id 列表为 [8,9,10,4,2],这样就造成了死锁。

3.3 不同索引锁冲突

这种情况比较隐晦,事务 A 在执行时,除了在二级索引加锁外,还会在聚簇索引上加锁,在聚簇索引上加锁的顺序是 [1,4,2,3,5],而事务 B 执行时,只在聚簇索引上加锁,加锁顺序是 [1,2,3,4,5],这样就造成了死锁的可能性。

3.4 gap 锁冲突

innodb 在 RR 级别下,如下的情况也会产生死锁,比较隐晦。不清楚的同学可以自行根据上节的 gap 锁原理分析下。

4 如何尽可能避免死锁

1)以固定的顺序访问表和行。比如对第 2 节两个 job 批量更新的情形,简单方法是对 id 列表先排序,后执行,这样就避免了交叉等待锁的情形;又比如对于 3.1 节的情形,将两个事务的 sql 顺序调整为一致,也能避免死锁。

2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。

3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。

4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从 RR 调整为 RC,可以避免掉很多因为 gap 锁造成的死锁。

5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

5 如何定位死锁成因

下面以本文开头的死锁案例为例,讲下如何排查死锁成因。

1)通过应用业务日志定位到问题代码,找到相应的事务对应的 sql;

因为死锁被检测到后会回滚,这些信息都会以异常反应在应用的业务日志中,通过这些日志我们可以定位到相应的代码,并把事务的 sql 给梳理出来。

start tran

1 deleteHeartCheckDOByToken

2 updateSessionUser

commit

此外,我们根据日志回滚的信息发现在检测出死锁时这个事务被回滚。

2)确定数据库隔离级别。

执行 select @@global.tx_isolation,可以确定数据库的隔离级别,我们数据库的隔离级别是 RC,这样可以很大概率排除 gap 锁造成死锁的嫌疑;

3)找 DBA 执行下 show InnoDB STATUS 看看最近死锁的日志。

这个步骤非常关键。通过 DBA 的帮忙,我们可以有更为详细的死锁信息。通过此详细日志一看就能发现,与之前事务相冲突的事务结构如下:

start tran

1 updateSessionUser

2 deleteHeartCheckDOByToken

commit

“怎么理解 MySQL wwwhj8828coml8o88O49999 死锁问题”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-24发表,共计3104字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)