线上发生死锁异常了,该怎么办

点击上方"程序员历小冰",选择“置顶或者星标”mysql

   你的关注意义重大!
程序员

前言

MySQL 死锁异常是咱们常常会遇到的线上异常类别,一旦线上业务日间复杂,各类业务操做之间每每会产生锁冲突,有些会致使死锁异常。这种死锁异常通常要在特定时间特定数据和特定业务操做才会复现,而且分析解决时还须要了解 MySQL 锁冲突相关知识,因此通常遇到这些偶尔出现的死锁异常,每每一时没有头绪,很差处理。web


本篇文章会讲解一下若是线上发生了死锁异常,如何去排查和处理。除了系列前文讲解的有关加锁和锁冲突的原理还,还须要对 MySQl 死锁日志和 binlog 日志进行分析。sql

正文

平常工做中,应对各种线上异常都要有咱们本身的 SOP (标准做业流程) ,这样不只可以提升本身的处理问题效率,也有助于将好的处理流程推广到团队,提升团队的总体处理异常能力。数据库

因此,面对线上偶发的 MySQL 死锁问题,个人排查处理过程以下:bash

  1. 线上错误日志报警发现死锁异常微信

  1. 查看错误日志的堆栈信息运维

  1. 查看 MySQL 死锁相关的日志分布式

  1. 根据 binlog 查看死锁相关事务的执行内容ide

  1. 根据上述信息找出两个相互死锁的事务执行的 SQL 操做,根据本系列介绍的锁相关理论知识,进行分析推断死锁缘由

  1. 修改业务代码


根据1,2步骤能够找到死锁异常时进行回滚事务的具体业务,也就可以找到该事务执行的 SQL 语句。而后咱们须要经过 3,4步骤找到死锁异常时另一个事务,也就是最终得到锁的事务所执行的 SQL 语句,而后再进行锁冲突相关的分析。

第一二步的线上错误日志和堆栈信息通常比较容易得到,第五步的分析 SQL 锁冲突缘由中涉及的锁相关的理论在系列文章中都有介绍,没有了解的同窗能够自行去阅读如下。

下面咱们就来重点说一下其中的第三四步骤,也就是如何查看死锁日志和 binlog 日志来找到死锁相关的 SQL 操做。

死锁日志的获取

发生死锁异常后,咱们能够直接使用 show engine innodb status 命令获取死锁信息,可是该命令只能获取最近一次的死锁信息。因此,咱们能够经过开启 InnoDB 的监控机制来获取实时的死锁信息,它会周期性(每隔 15 秒)打印 InnoDb 的运行状态到 mysqld 服务的错误日志文件中。

InnoDb 的监控较为重要的有标准监控(Standard InnoDB Monitor)和 锁监控(InnoDB Lock Monitor),经过对应的系统参数能够将其开启。

另外,MySQL 提供了一个系统参数 innodb_print_all_deadlocks 专门用于记录死锁日志,当发生死锁时,死锁日志会记录到 MySQL 的错误日志文件中。

另外,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 等状态。

其中第一行,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 的锁冲突

《锁类型和加锁原理》 一文中咱们说过,一共有四种类型的行锁:记录锁,间隙锁,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分和 Tom一、Teddy2 等 SQL 的具体数据信息在 binlog 找到对应的位置,具体以下图所示。

根据 binlog 的具体信息,咱们能够清晰的找到最终获取锁事务所执行的全部 SQL 语句,也就能找到其对应的业务代码,接下来咱们就能进行具体的锁冲突分析。

小结

死锁系列终于告一段落,若是大伙有什么疑问或者文中有什么错误,欢迎在下方留言讨论。也但愿你们继续持续关注。

-关注我

推荐阅读

TCP/IP的底层队列

基于Redis和Lua的分布式限流

AbstractQueuedSynchronizer超详细原理解析


本文分享自微信公众号 - 程序员历小冰(gh_a1d0b50d8f0a)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索