mysql死锁问题

  1. 云栖社区>
  2. 博客>
  3. 正文

mysql死锁问题

jephon 2016-08-11 12:12:00 浏览521
展开阅读全文

形成死锁的条件

我们mysql用的存储引擎是innodb,从日志来看,innodb主动探知到死锁,并回滚了某一苦苦等待的事务。

问题来了,innodb是怎么探知死锁的?

直观方法是在两个事务相互等待时,当一个等待时间超过设置的某一阀值时,对其中一个事务进行回滚,另一个事务就能继续执行。这种方法简单有效,在innodb中,参数innodb_lock_wait_timeout用来设置超时时间。

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

 wait-for graph原理:

当节点1需要等待节点2的资源时,就生成一条有向边指向节点2,最后形成一个有向图。我们只要检测这个有向图是否出现环路即可,出现环路就是死锁!这就是wait-for graph算法。

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

 

锁与索引的关系

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

 

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

 

              图4 聚簇索引和二级索引
下面分析下索引和锁的关系。

 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)未提交读(Read uncommitted);2)已提交读(Read committed(RC));3)可重复读(Repeatable read(RR));4)可串行化(Serializable)。我们较常使用的是RC和RR。

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

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

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

 

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

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

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

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

 

 推荐一篇好文,可以深入理解锁的原理:http://hedengcheng.com/?p=771#_Toc374698322

如何尽可能避免死锁

1)以固定的顺序访问表和行,将两个事务的sql顺序调整为一致,也能避免死锁。

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

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

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

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

如何定位死锁成因

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

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

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

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

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

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

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

 

网友评论

登录后评论
0/500
评论
jephon
+ 关注