MySQL死锁系列-线上死锁问题排查思路
bigegpt 2024-10-21 03:47 2 浏览
前言
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 语句,也就能找到其对应的业务代码,接下来我们就能进行具体的锁冲突分析。
小节
死锁系列终于告一段落,如果大伙有什么疑问或者文中有什么错误,欢迎在下方留言讨论。也希望大家继续持续关注。
作者:用户?
链接:https://juejin.im/post/6885315880444657678
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关推荐
- Java 泛型大揭秘:类型参数、通配符与最佳实践
-
引言在编程世界中,代码的可重用性和可维护性是至关重要的。为了实现这些目标,Java5引入了一种名为泛型(Generics)的强大功能。本文将详细介绍Java泛型的概念、优势和局限性,以及如何在...
- K8s 的标签与选择器:流畅运维的秘诀
-
在Kubernetes的世界里,**标签(Label)和选择器(Selector)**并不是最炫酷的技术,但却是贯穿整个集群管理与运维流程的核心机制。正是它们让复杂的资源调度、查询、自动化运维变得...
- 哈希Hash算法:原理、应用(哈希算法 知乎)
-
原作者:Linux教程,原文地址:「链接」什么是哈希算法?哈希算法(HashAlgorithm),又称为散列算法或杂凑算法,是一种将任意长度的数据输入转换为固定长度输出值的数学函数。其输出结果通常被...
- C#学习:基于LLM的简历评估程序(c# 简历)
-
前言在pocketflow的例子中看到了一个基于LLM的简历评估程序的例子,感觉还挺好玩的,为了练习一下C#,我最近使用C#重写了一个。准备不同的简历:image-20250528183949844查...
- 55顺位,砍41+14+3!季后赛也成得分王,难道他也是一名球星?
-
雷霆队最不可思议的新星:一个55号秀的疯狂逆袭!你是不是也觉得NBA最底层的55号秀,就只能当饮水机管理员?今年的55号秀阿龙·威金斯恐怕要打破你的认知了!常规赛阶段,这位二轮秀就像开了窍的天才,直接...
- 5分钟读懂C#字典对象(c# 字典获取值)
-
什么是字典对象在C#中,使用Dictionary类来管理由键值对组成的集合,这类集合被称为字典。字典最大的特点就是能够根据键来快速查找集合中的值,其键的定义不能重复,具有唯一性,相当于数组索引值,字典...
- c#窗体传值(c# 跨窗体传递数据)
-
在WinForm编程中我们经常需要进行俩个窗体间的传值。下面我给出了两种方法,来实现传值一、在输入数据的界面中定义一个属性,供接受数据的窗体使用1、子窗体usingSystem;usingSyst...
- C#入门篇章—委托(c#委托的理解)
-
C#委托1.委托的定义和使用委托的作用:如果要把方法作为函数来进行传递的话,就要用到委托。委托是一个类型,这个类型可以赋值一个方法的引用。C#的委托通过delegate关键字来声明。声明委托的...
- C#.NET in、out、ref详解(c#.net framework)
-
简介在C#中,in、ref和out是用于修改方法参数传递方式的关键字,它们决定了参数是按值传递还是按引用传递,以及参数是否必须在传递前初始化。基本语义对比修饰符传递方式可读写性必须初始化调用...
- C#广义表(广义表headtail)
-
在C#中,广义表(GeneralizedList)是一种特殊的数据结构,它是线性表的推广。广义表可以包含单个元素(称为原子),也可以包含另一个广义表(称为子表)。以下是一个简单的C#广义表示例代...
- 「C#.NET 拾遗补漏」04:你必须知道的反射
-
阅读本文大概需要3分钟。通常,反射用于动态获取对象的类型、属性和方法等信息。今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的。获取类型的成员Type类的GetMembe...
- C#启动外部程序的问题(c#怎么启动)
-
IT&OT的深度融合是智能制造的基石。本公众号将聚焦于PLC编程与上位机开发。除理论知识外,也会结合我们团队在开发过程中遇到的具体问题介绍一些项目经验。在使用C#开发上位机时,有时会需要启动外部的一些...
- 全网最狠C#面试拷问:这20道题没答出来,别说你懂.NET!
-
在竞争激烈的C#开发岗位求职过程中,面试是必经的一道关卡。而一场高质量的面试,不仅能筛选出真正掌握C#和.NET技术精髓的人才,也能让求职者对自身技术水平有更清晰的认知。今天,就为大家精心准备了20道...
- C#匿名方法(c#匿名方法与匿名类)
-
C#中的匿名方法是一种没有名称只有主体的方法,它提供了一种传递代码块作为委托参数的技术。以下是关于C#匿名方法的一些重要特点和用法:特点省略参数列表:使用匿名方法可省略参数列表,这意味着匿名方法...
- C# Windows窗体(.Net Framework)知识总结
-
Windows窗体可大致分为Form窗体和MDI窗体,Form窗体没什么好细说的,知识点总结都在思维导图里面了,下文将围绕MDI窗体来讲述。MDI(MultipleDocumentInterfac...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)