MySQL死锁系列-线上死锁问题排查思路
前言
MySQL 死锁异常是我们经常会遇到的线上异常类别,一旦线上业务日间复杂,各种业务操作之间往往会产生锁冲突,有些会导致死锁异常。这种死锁异常一般要在特定时间特定数据和特定业务操作才会复现,并且分析解决时还需要了解 MySQL 锁冲突相关知识,所以一般遇到这些偶尔出现的死锁异常,往往一时没有头绪,不好处理。
本篇文章会讲解一下如果线上发生了死锁异常,如何去排查和处理。除了系列前文讲解的有关加锁和锁冲突的原理还,还需要对 MySQl 死锁日志和 binlog 日志进行分析。

正文
**日常工作中,应对各类线上异常都要有我们自己的 SOP (标准作业流程) ** ,这样不仅能够提高自己的处理问题效率,也有助于将好的处理流程推广到团队,提高团队的整体处理异常能力。
所以,面对线上偶发的 MySQL 死锁问题,我的排查处理过程如下:
线上错误日志报警发现死锁异常
查看错误日志的堆栈信息
查看 MySQL 死锁相关的日志
根据 binlog 查看死锁相关事务的执行内容
根据上述信息找出两个相互死锁的事务执行的 SQL 操作,根据本系列介绍的锁相关理论知识,进行分析推断死锁原因
修改业务代码
根据1,2步骤可以找到死锁异常时进行回滚事务的具体业务,也就能够找到该事务执行的 SQL 语句。然后我们需要通过 3,4步骤找到死锁异常时另外一个事务,也就是最终获得锁的事务所执行的 SQL 语句,然后再进行锁冲突相关的分析。
第一二步的线上错误日志和堆栈信息一般比较容易获得,第五步的分析 SQL 锁冲突原因中涉及的锁相关的理论在系列文章中都有介绍,没有了解的同学可以自行去阅读以下。
下面我们就来重点说一下其中的第三四步骤,也就是如何查看死锁日志和 binlog 日志来找到死锁相关的 SQL 操作。
死锁日志的获取
发生死锁异常后,我们可以直接使用 show engine innodb status 命令获取死锁信息,但是该命令只能获取最近一次的死锁信息。所以,我们可以通过开启 InnoDB 的监控机制来获取实时的死锁信息,它会周期性(每隔 15 秒)打印 InnoDb 的运行状态到 mysqld 服务的错误日志文件中。
InnoDb 的监控较为重要的有标准监控(Standard InnoDB Monitor)和 锁监控(InnoDB Lock Monitor),通过对应的系统参数可以将其开启。
-- 开启标准监控
set GLOBAL innodb_status_output=ON;
-- 关闭标准监控
set GLOBAL innodb_status_output=OFF;
-- 开启锁监控
set GLOBAL innodb_status_output_locks=ON;
-- 关闭锁监控
set GLOBAL innodb_status_output_locks=OFF;
另外,MySQL 提供了一个系统参数 innodb_print_all_deadlocks 专门用于记录死锁日志,当发生死锁时,死锁日志会记录到 MySQL 的错误日志文件中。
set GLOBAL innodb_print_all_deadlocks=ON;
死锁日志的分析
通过上述手段,我们可以拿到死锁日志,下图是我做实验触发死锁异常时获取的日志(省略的部分信息)。

该日志会列出死锁发生的时间,死锁相关的事务,并显示出两个事务(可惜,多事务发生死锁时,也只显示两个事务)在发生死锁时执行的 SQL 语句、持有或等待的锁信息和最终回滚的事务。
下面,我们来一段一段的解读该日志中给出的信息,我们按照图中标注的顺序来介绍:
TRANSACTION 2078, ACTIVE 74 sec starting index read // -1 事务一的基础信息,包括事务ID、活跃时间,当前运行状态
表示的是 ACTIVE 74 sec 表示事务活动时间,starting index read 为事务当前正在运行的状态,可能的事务状态有:fetching rows,updating,deleting,inserting, starting index read 等状态。
mysql tables in use 1, locked 1 // -2 使用一个table,并且有一个表锁
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 // -3 涉及的锁结构和内存大小
tables in use 1 表示有一个表被使用,locked 1 表示有一个表锁。LOCK WAIT 表示事务正在等待锁,3 lock struct(s) 表示该事务的锁链表的长度为 3,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁或 autoinc 锁等。heap size 1136 为事务分配的锁堆内存大小。
2 row lock(s) 表示当前事务持有的行锁个数,通过遍历上面提到的 11 个锁结构,找出其中类型为 LOCK_REC 的记录数。undo log entries 1 表示当前事务有 1 个 undo log 记录,说明该事务已经更新了 1条记录。
下面就是死锁日志中最为重要的持有或者待获取锁信息,如图中-5和-6行所示,通过它可以分析锁的具体类型和涉及的表,这些信息能辅助你按照系列文章的锁相关的知识来分析 SQL 的锁冲突。
RECORD LOCKS space id 2 page no 4 n bits 80 index PRIMARY of table `test`.`t` trx id 2078 lock_mode X locks rec but not gap // -5 具体持有锁的信息
RECORD LOCKS space id 2 page no 4 n bits 80 index PRIMARY of table `test`.`t` trx id 2078 lock_mode X locks rec but not gap waiting // -6 等待获取锁的信息
在 《锁类型和加锁原理》 一文中我们说过,一共有四种类型的行锁:记录锁,间隙锁,Next-key 锁和插入意向锁。这四种锁对应的死锁日志各不相同,如下:
- 记录锁(LOCK_REC_NOT_GAP): lock_mode X locks rec but not gap
- 间隙锁(LOCK_GAP): lock_mode X locks gap before rec
- Next-key 锁(LOCK_ORNIDARY): lock_mode X
- 插入意向锁(LOCK_INSERT_INTENTION): lock_mode X locks gap before rec insert intention
所以,按照死锁日志,我们发现事务一持有了 test.t 表上的记录锁,并且等待另一个记录锁。
通过死锁日志,我们可以找到最终获得锁事务最后执行的 SQL,但是如果该事务执行了多条 SQL,这些信息就可能不够用的啦,我们需要完整的了解该事务所有执行的 SQL语句。这时,我们就需要从 binlog 日志中获取。
binlog的获取和分析
binlog 日志会完整记录事务执行的所有 SQL,借助它,我们就能找到最终获取锁事务所执行的全部 SQL。然后再进行具体的锁冲突分析。
我们可以使用 MySQL 的命令行工具 Mysqlbinlog 远程获取线上数据库的 binlog 日志。具体命令如下所示:
Mysqlbinlog -h127.0.0.1 -u root -p --read-from-remote-server binlog.000001 --base64-output=decode-rows -v
其中 --base64-output=decode-rows 表示 row 模式 binlog日志,所以该方法只适用于 row 模式的 binlog日志,但是目前主流 MySQL 运维也都是把 binlog 日志设置为 row 模式,所以这点限制也就无伤大雅。-v 则表示将行事件重构成被注释掉的伪SQL语句。
我们可以通过死锁日志中死锁发生的具体事件和最终获取锁事务正在执行的SQL的参数信息找到 binlog 中该事务的对应信息,比如我们可以直接通过死锁日志截图中的具体的时间 10点57分和 Tom1、Teddy2 等 SQL 的具体数据信息在 binlog 找到对应的位置,具体如下图所示。

根据 binlog 的具体信息,我们可以清晰的找到最终获取锁事务所执行的所有 SQL 语句,也就能找到其对应的业务代码,接下来我们就能进行具体的锁冲突分析。
小节
死锁系列终于告一段落,如果大伙有什么疑问或者文中有什么错误,欢迎在下方留言讨论。也希望大家继续持续关注。

MySQL死锁系列-线上死锁问题排查思路的更多相关文章
- 记一次linux通过jstack定位CPU使用过高问题或排查线上死锁问题
一.java定位进程 在服务器中终端输入命令:top 可以看到进程ID,为5421的cpu这列100多了. 记下这个数字:5421 二.定位问题进程对应的线程 然后在服务器中终端输入命令:top -H ...
- 【JVM】线上应用故障排查
高CPU占用 一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环. 根据top命令,发现PID为28555的Java进程占用CPU高达200%,出现故障. 通过ps aux ...
- 一次线上OOM故障排查经过
转贴:http://my.oschina.net/flashsword/blog/205266 本文是一次线上OOM故障排查的经过,内容比较基础但是真实,主要是记录一下,没有OOM排查经验的同学也可以 ...
- Java线上应用故障排查之二:高内存占用
搞Java开发的,经常会碰到下面两种异常: 1.java.lang.OutOfMemoryError: PermGen space 2.java.lang.OutOfMemoryError: Java ...
- java线上应用故障排查之二:高内存占用【转】
前一篇介绍了线上应用故障排查之一:高CPU占用,这篇主要分析高内存占用故障的排查. 搞Java开发的,经常会碰到下面两种异常: 1.java.lang.OutOfMemoryError: PermGe ...
- MySQL慢日志线上问题分析及功能优化
本文来源于数据库内核专栏. MySQL慢日志(slow log)是MySQL DBA及其他开发.运维人员需经常关注的一类信息.使用慢日志可找出执行时间较长或未走索引等SQL语句,为进行系统调优提供依据 ...
- 线上服务器CPU100%排查,Linux进程消耗查看
线上服务器CPU100%排查,Linux进程消耗查看 1.排查步骤 1.1Linux下排查 1.1.1查消耗cpu最高的进程PID 1.1.2根据PID查出消耗cpu最高的线程号 1.1.3根据线程号 ...
- mysql innodb引擎 一次线上死锁分析排查步骤
我们的线上erp系统一天使用人员反映部分数据死活保存不上而且页面操作很慢.开始以为操作数据量大的原因, 后来查看了我们线上的glowroot系统,发现slowtrace中有超长时间的访问,点开查看详情 ...
- 一次 MySQL 线上死锁分析实战
关键词:MySQL Index Merge 前言 MySQL 的锁机制相信大家在学习 MySQL 的时候都有简单的了解过,那既然有锁就必定绕不开死锁这个问题.其实 MySQL 在大部分场景下是不会存在 ...
随机推荐
- 装个Mysql怎么就这么难?
2020/5/20 程序员是没有女朋友的~~ 声明:这篇文章是我第一次安装MySQL,主要记录了安装的艰辛过程,内容很凌乱,不适合作为参考. CentOS 6.10安装MySQL一直出问题,换了各种不 ...
- Linux自动同步时间的方法
介绍两种同步linux系统的时间方法: (1) 开启ntpd服务,即配置ntp服务器实现时间同步. (2) 利用ntp客户端程序,即ntpdate同步时间. 注意:因为ntp服务器本来就会与上层时间服 ...
- Github上如何添加 LICENSE 文件?
什么是开源软件? 开源软件是所有人都可以修改和补充的软件,因为开源软件的 license 协议允许这样做. Git版本控制系统就是开源的软件. 实际上开源软件的一个重要组成部分就是添加 license ...
- yum管理——linux字符界面安装图形化及两种界面的切换(3)
1.查看yum软件包组 yum groups list 2.选择安装带 GUI 的服务器 yum groups install "带 GUI 的服务器" 3.字符界面切换为图形化界 ...
- google protocol buffer——protobuf的问题和改进2
这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们举例了 ...
- ES6 常用总结——第二章(字符串的扩展)
ES6为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历. for (let codePoint of 'foo') { console.log(codePoint)} // &quo ...
- 面试题总结:可能是全网最好的MySQL重要知识点
标题有点标题党的意思,但希望你在看了文章之后不会有这个想法--这篇文章是作者对之前总结的 MySQL 知识点做了完善后的产物,可以用来回顾MySQL基础知识以及备战MySQL常见面试问题. 什么是My ...
- 刷题[SUCTF 2018]GetShell
解题思路 进入upload,发现有源码,代码审计 代码审计 大概意思就是,读取我们上传的文件,截取第五位之后的内容,与黑名单不匹配才能上传成功 我传的是一个空的txt文件,发现会变成php文件,那好办 ...
- DC 1-3 靶机渗透
DC-1靶机 端口加内网主机探测,发现192.168.114.146这台主机,并且开放了有22,80,111以及48683这几个端口. 发现是Drupal框架. 进行目录的扫描: 发现admin被禁止 ...
- spark源码分析, 任务反序列化及执行
1 ==> 接受消息,org.apache.spark.executor.CoarseGrainedExecutorBackend#receive case LaunchTask(data) = ...