线上某服务时不时报出如下异常(大约一天二十多次):“Deadlock found when trying to get lock;”。

Oh, My God! 是死锁问题。尽管报错不多,对性能目前看来也无太大影响,但还是需要解决,保不齐哪天成为性能瓶颈。
     为了更系统的分析问题,本文将从死锁检测、索引隔离级别与锁的关系、死锁成因、问题定位这五个方面来展开讨论。

图1 应用日志

1 死锁是怎么被发现的?

1.1 死锁成因&&检测方法

左图那两辆车造成死锁了吗?不是!右图四辆车造成死锁了吗?是!

                                                                      图2 死锁描述

我们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算法。
                                                                                            图3 wait for graph

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

1.2 innodb隔离级别、索引与锁

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

1.2.1 锁与索引的关系

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

id: bigint

token: varchar(30)

message: varchar(4096)

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

图4 聚簇索引和二级索引
     下面分析下索引和锁的关系。
1)delete from msg where id=2;

由于id是主键,因此直接锁住整行记录即可。
                                                                               图5
2)delete from msg where token=’ cvs’;

由于token是二级索引,因此首先锁住二级索引(两行),接着会锁住相应主键所对应的记录;
                                                                       图6
3)delete from msg where message=订单号是多少’;

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

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级别下尽管加了行锁,但还是避免不了幻读。

                                                                     图8

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锁被释放。这样就能避免幻读问题。
                                                                           图9

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

3 死锁成因

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

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

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

                                                                       图10

3.2相同表记录行锁冲突

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

                                                                          图11

3.3不同索引锁冲突

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

                                                                          图12

3.4 gap锁冲突

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

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

  这不就是图10描述的死锁嘛!

mysql死锁问题分析的更多相关文章

  1. MySQL 死锁问题分析

    转载: MySQL 死锁问题分析 线上某服务时不时报出如下异常(大约一天二十多次):"Deadlock found when trying to get lock;". Oh, M ...

  2. MySQL死锁案例分析与解决方案

    MySQL死锁案例分析与解决方案 现象: 数据库查询: SQL语句分析:  mysql. 并发delete同一行记录,偶发死锁.   delete from x_table where id=?   ...

  3. MySQL死锁问题分析及解决方法实例详解(转)

      出处:http://www.jb51.net/article/51508.htm MySQL死锁问题是很多程序员在项目开发中常遇到的问题,现就MySQL死锁及解决方法详解如下: 1.MySQL常用 ...

  4. mysql死锁问题分析(转)

    线上某服务时不时报出如下异常(大约一天二十多次):“Deadlock found when trying to get lock;”. Oh, My God! 是死锁问题.尽管报错不多,对性能目前看来 ...

  5. MySQL 死锁日志分析

    ------------------------ LATEST DETECTED DEADLOCK ------------------------ 140824  1:01:24 *** (1) T ...

  6. MySQL死锁原因分析

    行级锁有三种模式: innodb 行级锁 record-level lock大致有三种:record lock, gap lock and Next-KeyLocks. record lock  锁住 ...

  7. Mysql死锁问题解决方式 & 聚簇索引、隔离级别等知识

    参考了这篇文章:http://www.cnblogs.com/LBSer/p/5183300.html  <mysql死锁问题分析> 写的不错. 如果Mysql死锁,会报出: 1.1 死锁 ...

  8. MySQL 死锁与日志二三事

    最近线上 MySQL 接连发生了几起数据异常,都是在凌晨爆发,由于业务场景属于典型的数据仓库型应用,白天压力较小无法复现.甚至有些异常还比较诡异,最后 root cause 分析颇费周折.那实际业务当 ...

  9. MySQL死锁及解决方案

    一.MySQL锁类型 1. MySQL常用存储引擎的锁机制 MyISAM和MEMORY采用表级锁(table-level locking) BDB采用页面锁(page-level locking)或表 ...

随机推荐

  1. ios基础篇(十六)——UIWebView的基本使用

    UIWebView是内置的浏览器控件,可以用它来浏览网页.打开文档等.UIWebView是一个混合体,具体的功能控件内置的,实现一些基本的功能.UIWebView可以查看Html网页,pdf文件,do ...

  2. IT的灵魂是流程,流程的灵魂是业务,业务的灵魂是战略

    IT的灵魂是流程,流程的灵魂是业务,业务的灵魂是战略.高效的IT平台不在于IT技术,而在于好的管理模式与流程设计 从以组织为核心转向以流程为核心 流程管理核心是从流程角度出发,关注流程是否增值,籍此建 ...

  3. 动态监控驱动、dll、exe加载

    /* windows2003 x86/x64 window7 x86 windows2008 R2 x64测试通过 */ #include <ntddk.h> #include " ...

  4. Ubuntu-搜狗输入法

    Sougou(搜狗):要换fictx输入法,先删除ibus输入法.搜索sudo apt-get purge ibussudo apt-get autoremove 然后安装fcitx和拼音输入法(要安 ...

  5. Android_Layout (一)

    layout (布局)  --->Android 有五大布局,分别是: LinearLayout : 线性布局,子组件按照垂直或者水平方向来布局. RelativeLayout :相对布局,按照 ...

  6. 关于QGraphicsScene 和 QGraphicsView 和 QDialog 的杂乱笔记【或说指针复习。。】

    LtCalibrateDlg::~LtCalibrateDlg() { if (m_pIplImageGray) cvReleaseImage(&m_pIplImageGray); MYDEL ...

  7. 制作.frameWork的最全最真实的解决办法

    这个制作流程 本博主 已经完全测试成功 我这边 制作的.frameWork 要接入游戏 我们游戏已经上架 所以这个东西完全可以用 http://www.cocoachina.com/bbs/read. ...

  8. 2、JS中的表达式和运算符

    一.表达式 1.原始表达式:2.14,“test”,true/false,null……复合表达式:10*20…… 2.数组.对象的初始化表达式:new Array(1,2),[1,undefined, ...

  9. 自动机理论、语言和计算导论 by John E. Hopcroft

    计算理论是计算机应用的基础,理论和应用缺一而不可. ---- 目录 ---- C01 自动机 C02 有穷自动机 C03 正则表达式与正则语言 C04 正则语言的性质 C05 上下文无关文法及上下文无 ...

  10. Spring 和SpringMVC 的父子容器关系

      Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而实际使用时,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但是在实际项目中,同时配 ...