一、导读

在 MySQL 中,当两个或以上的事务相互持有和请求锁,并形成一个循环的依赖关系,就会产生死锁。在一个事务系统中,死锁是确切存在并且是不能完全避免的。 InnoDB 会自动检测事务死锁,立即回滚其中某个事务,并且返回一个错误。它根据某种机制来选择那个最简单(代价最小)的事务来进行回滚。偶然发生的死锁不必担心,但死锁频繁出现的时候就要引起注意了。

在 MySQL 5.6 之前,只有最新的死锁信息可以使用 show engine innodb status 命令来进行查看。使用 Percona Toolkit 工具包中的 pt-deadlock-logger 可以从 show engine innodb status 的结果中得到指定的时间范围内的死锁信息,同时写入文件或者表中,等待后面的诊断分析。对于 pt-deadlock-logger 工具的更多信息可以参考手册:https://www.percona.com/doc/percona-toolkit/2.2/pt-deadlock-logger.html 。 如果使用的是 MySQL 5.6 或以上版本,您可以启用一个新增的参数 innodb_print_all_deadlocks 把 InnoDB 中发生的所有死锁信息都记录在错误日志里面。

在开始讲诊断之前,得到应用程序捕捉到的死锁错误(MySQL error no. 1213),以及通过重试来处理失败事务是一个重要的做法。

二、如何诊断MySQL死锁

一个 MySQL 死锁可能会涉及到两个或以上的事务,而 LATEST DETECTED DEADLOCK 这一节仅仅展示了最后两个事务,并且只显示这两个事务中最后执行的一条语句和循环依赖中的锁信息。而忽略掉了前面执行过的有可能真正申请并持有锁的语句。下面我会给大家讲一些如何收集这些被忽略掉的语句的 tips。

让我们一起来看两个例子,看 LATEST DETECTED DEADLOCK 都给出了什么信息。例 1:

1 141013 6:06:22

2 *** (1) TRANSACTION:

3 TRANSACTION 876726B90, ACTIVE 7 sec setting auto-inc lock

4 mysql tables in use 1, locked 1

5 LOCK WAIT 9 lock struct(s), heap size 1248, 4 row lock(s), undo log entries 4

6 MySQL thread id 155118366, OS thread handle 0x7f59e638a700, query id87987781416 localhost msandbox update

7 INSERT INTO t1 (col1, col2, col3, col4) values (10, 20, 30, 'hello')

8 *** (1) WAITING FOR THIS LOCK TO BE GRANTED:

9 TABLE LOCK table `mydb`.`t1` trx id 876726B90 lock mode AUTO-INC waiting

10 *** (2) TRANSACTION:

11 TRANSACTION 876725B2D, ACTIVE 9 sec inserting

12 mysql tables in use 1, locked 1

13 876 lock struct(s), heap size 80312, 1022 row lock(s), undo log entries 1002

14 MySQL thread id 155097580, OS thread handle 0x7f585be79700, query id87987761732 localhost msandbox update

15 INSERT INTO t1 (col1, col2, col3, col4) values (7, 86, 62, "a lot of things"),(7, 76, 62, "many more")

16 *** (2) HOLDS THE LOCK(S):

17 TABLE LOCK table `mydb`.`t1` trx id 876725B2D lock mode AUTO-INC

18 *** (2) WAITING FOR THIS LOCK TO BE GRANTED:

19 RECORD LOCKS space id 44917 page no 529635 n bits 112 index `PRIMARY` of table`mydb`.`t2` trx id 876725B2D lock mode S locks rec but not gap waiting

20 *** WE ROLL BACK TRANSACTION (1)

第 1 行是死锁发生的时间。如果你的应用程序捕捉和记录死锁错误到日志中,那么你可以根据这个时间戳和应用程序日志中的死锁错误的时间戳进行匹配。这样你可以得到已回滚的事务,及事务中的所有语句。

第 3 和 11 行,注意事务的序号和活跃时间。如果你定期地把 show engine innodb status 的输出信息记录到日志文件(这是一个很好的做法),那么你就可以使用事务编号在之前的输出日志中查到同一个事务中所希望看到的更多的语句。活跃时间提供了一个线索来判断这个事务是单个语句的事务,还是包含多个语句的事务。

第 4 和 12 行,使用到的表和锁只是针对于当前的语句。因此,使用到一张表,并不意味着事务仅仅涉及到一张表。

第 5 和 13 行,这里的信息需要重点关注,因为它告诉我们事务做了多少的改变,也就是 "undo log entries";"row lock(s)" 则告诉我们持有多少行锁。这些信息都会提示我们这个事务的复杂程度。

第 6 和 14 行,留意线程 ID、连接主机和用户。如果你在不同的应用程序中使用不同的 MySQL 用户,这将是另外一个好的习惯,这样你就可以根据连接主机和用户来定位到事务来自于哪个应用程序。

第 9 行,对于第一个事务,它只是显示了处于锁等待状态,在这个例子中,是表 t1 的 AUTO_INC 锁。其他的可能:共享锁(S),有间隙锁(gap lock)的排他锁(X),及没有间隙锁(gap lock)的排他锁(X)。

第 16 和 17 行,对于第二个事务,显示了它持有的锁,在本示例中,是事务1 (TRANSACTION (1)) 所请求并等待中的 AUTO-INC 锁。

第 18 和 19 行,显示了事务2 (TRANSACTION (2)) 所等待的锁的信息。在本例中,是一个在另一个表 t2 的主键上面共享的没有间隙的记录锁。在 InnoDB 中只有少数情况会产生 共享记录锁:

1) 使用了 SELECT … LOCK IN SHARE MODE 的语句

2) 外键引用记录

3) 源表上的共享锁,使用了 INSERT INTO… SELECT 的语句

事务2 的当前语句是一个简单的 insert to t1,所以 1 和 3 被排除了。通过检查 show create table t1,你可以确定共享锁(S) 是由于父表t2 的外键一致性约束。

例 2:使用 MySQL 社区版,每个记录锁的记录内容都会被打印出来。

1 2014-10-11 10:41:12 7f6f912d7700

2 *** (1) TRANSACTION:

3 TRANSACTION 2164000, ACTIVE 27 sec starting index read

4 mysql tables in use 1, locked 1

5 LOCK WAIT 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1

6 MySQL thread id 9, OS thread handle 0x7f6f91296700, query id 87 localhost ro otupdating

7 update t1 set name = 'b' where id = 3

8 *** (1) WAITING FOR THIS LOCK TO BE GRANTED:

9 RECORD LOCKS space id 1704 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1`trx id 2164000 lock_mode X locks rec but not gap waiting

10 Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bit s0

11 0: len 4; hex 80000003; asc ;;

12 1: len 6; hex 000000210521; asc ! !;;

13 2: len 7; hex 180000122117cb; asc ! ;;

14 3: len 4; hex 80000008; asc ;;

15 4: len 1; hex 63; asc c;;

16

17 *** (2) TRANSACTION:

18 TRANSACTION 2164001, ACTIVE 18 sec starting index read

19 mysql tables in use 1, locked 1

20 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1

21 MySQL thread id 10, OS thread handle 0x7f6f912d7700, query id 88 localhost root updating

22 update t1 set name = 'c' where id = 2

23 *** (2) HOLDS THE LOCK(S):

24 RECORD LOCKS space id 1704 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1`trx id 2164001 lock_mode X locks rec but not gap

25 Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bit s0

26 0: len 4; hex 80000003; asc ;;

27 1: len 6; hex 000000210521; asc ! !;;

28 2: len 7; hex 180000122117cb; asc ! ;;

29 3: len 4; hex 80000008; asc ;;

30 4: len 1; hex 63; asc c;;

31

32 *** (2) WAITING FOR THIS LOCK TO BE GRANTED:

33 RECORD LOCKS space id 1704 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1`trx id 2164001 lock_mode X locks rec but not gap waiting

34 Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bit s0

35 0: len 4; hex 80000002; asc ;;

36 1: len 6; hex 000000210520; asc ! ;;

37 2: len 7; hex 17000001c510f5; asc ;;

38 3: len 4; hex 80000009; asc ;;

39 4: len 1; hex 62; asc b;;

第 9 和 10 行:"space id" 是表空间 ID,"page no" 指出了这个表空间里面记录锁所在的数据页,"n bits" 不是数据页偏移量,而是锁位图里面的 bits 数。在第 10 行记录的 "heap no" 是数据页偏移量。

第 11 到 15 行:显示了记录数据的十六进制编码。字段 0 表示聚集索引(即主键),忽略最高位,值为 3。字段 1 表示最后修改这条记录的事务的ID号,上面实例中的十进制值是 2164001,即是 TRANSACTION (2)。字段 2 表示回滚指针。从字段 3 开始,表示的是余下的行数据:字段 3 表示一个整型列,值为 8,字段 4 表示一个字符串列,值为 'c'。通过阅读这些信息,我们可以准确知道哪一行被锁了,哪些是当前值。

三、我们还可以从分析中学到什么

既然大多数 MySQL 死锁是发生在两个事务之间,那么我们可以基于这个假设来进行分析。在例1 中,事务2 (trx(2)) 在等待一个共享锁,因此事务1 (trx(1)) 在表 t2 的主键记录上,要么持有一个共享锁,要么持有一个排他锁。假设 col2 是一个外键列,通过检查 trx(1) 的当前语句,我们可以知道它不需要相同的记录锁,因而它肯定是一些在 trx(1) 之前就已经申请了在 t2 主键索引上的 S 或者 X 锁的语句。trx(1) 在 7 秒钟里面仅改变了 4 行数据。

因此,我们可以得到 trx(1) 的一些特征:它做了很多处理,却做了很少变化;变化涉及到 t1 和 t2,单个记录插入到 t2。这些信息结合着其他数据可以帮助开发人员定位到那个事务。

四、我们还可以从哪里找到事物之前的语句

除了应用程序日志和之前的 show engine innodb status 的输出信息外,还可以利用 binlog、low log,甚至是 general log。

通过 binlog,如果 binlog_format = statement,binlog 中的每个 event 都会拥有一个 thread_id。只有已提交的事务会被记录到 binlog 中,因此,我们只能在 binlog 中查找 trx(2) 的细信息。在例1 中,我们知道死锁发生的时间,还有 trx(2) 是在 9 秒前开始的。我们可以执行 mysqlbinlog 命令来解析对应的 binlog 文件,并且寻找带有 thread_id = 155097580 的语句。使用应用程序代码来进行交叉确认也是很好的。

$ mysqlbinlog -vvv --start-datetime=“2014-10-13 6:06:12” --stop-datatime=“2014-10-136:06:22” mysql-bin.000010 > binlog_1013_0606.out

在 Percona Server 5.5 或者更早版本,可以通过设置 log_slow_verbosity 选项来把 事务 ID 记录在 slow log 中。如果你设置了 long_query_time = 0,你可以捕获到包括那些回滚到 slow log 里面的所有语句。通过 general log,可以用 thread id 来查找相关的语句。

五、如何避免死锁

在了解死锁之后,我们可以做一些事情来避免它。

对应用程序进行调整/修改。在某些情况下,你可以通过把大事务分解成多个小事务,使得锁能够更快被释放,从而极大程度地降低死锁发生的频率。在其他情况下,死锁的发生是因为两个事务采用不同的顺序操作了一个或多个表的相同的数据集。需要改成以相同顺序读写这些数据集,换言之,就是对这些数据集的访问采用串行化方式。这样在并发事务时,就让死锁变成了锁等待。

修改表的 schema,例如删除外键约束来分离两张表,或者添加索引来减少扫描和锁定的行。

如果发生了间隙锁,你可以把会话或者事务的事务隔离级别更改为 RC(read committed)级别来避免,但此时需要把 binlog_format 设置成 row 或者 mixed 格式。

本文来自云栖社区合作伙伴“DBGEEK”

何金龙 mysql_[译文]MySQL发生死锁肿么办?by何金龙相关推荐

  1. mysql发生死锁的必要条件_mysql死锁解读

    死锁(Deadlock) 什么是死锁 所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁, ...

  2. 多程序同时操作 mysql_关于多个程序同时操作一个表发生死锁的问题

    锁的概述 一. 为什么要引入锁 多个用户同时对数据库的并发操作时会带来以下数据不一致的问题: 丢失更新 A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统 ...

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

    前言 MySQL 死锁异常是我们经常会遇到的线上异常类别,一旦线上业务日间复杂,各种业务操作之间往往会产生锁冲突,有些会导致死锁异常.这种死锁异常一般要在特定时间特定数据和特定业务操作才会复现,并且分 ...

  4. Java多线程、高并发秒杀时MySQL出现死锁原因(Deadlock found when trying to get lock)及对应解决方案

    1. 死锁背景 1.1 在做高并发秒杀中创建订单.减库存步骤时出现异常:MySQLTransactionRollbackException: Deadlock found when trying to ...

  5. MySQL笔记-死锁原理与分析及InnoDB中如何减少死锁

    根据InnoDB的加锁规则(Record Lock.Gap Lock.meta data lock)可以写出不会发生死锁的SQL语句,也能定位出产生死锁的原因. 死锁产生的原因: 产生回路:两个或两个 ...

  6. 关于MySQL的死锁问题

    本文来说下关于MySQL的死锁问题 文章目录 什么是死锁 InnoDB锁类型 间隙锁( gap lock ) next-key lock 意向锁( Intention lock ) 插入意向锁( In ...

  7. mysql 表死锁_MySQL Innodb表导致死锁日志情况分析与归纳

    案例描述在定时脚本运行过程中,发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志. 两个sql语句如下:(1)insert into backup ...

  8. MySQL数据库死锁了,该怎么办?一文全解最新教程

    文章目录 正文 死锁的发生 为什么会产生死锁? Insert 语句是怎么加行级锁的? 1.记录之间加有间隙锁 2.遇到唯一键冲突 如何避免死锁? 之前分享过 MySQL 死锁的文章,然后很多读者对「插 ...

  9. mysql 代替不明_所有的死锁,都是不明就里的循环。又一必须升级MySQL的死锁问题!!!...

    原标题:所有的死锁,都是不明就里的循环.又一必须升级MySQL的死锁问题!!! 去年年底到今年年初,线上发生了3次MySQL数据库hang住的情况.在内部,我们将其称为半死不活的场景.具体的表现表现如 ...

最新文章

  1. 对约束条件优化问题的理解
  2. 安装class-dump
  3. JAVA: List用法
  4. java8 map reduce 分组_java8快速实现List转map 、分组、过滤等操作
  5. 【Python数据预处理】 归一化(按列减均值,除方差),标准化(按列缩放到指定范围),正则化(范数)
  6. 如何洞察行业中的应用场景?(下篇)
  7. 【JAVA基础篇】内部类
  8. 【操作系统】进程的创建与终止过程中的父子进程
  9. 麒麟信安:“一云多芯”信创云桌面解决方案引领者
  10. 20个全屏响应式菜单效果荟萃
  11. 算法:Invert Binary Tree(翻转二叉树)
  12. cuda历史版本下载
  13. Linux rpm命令
  14. 高效非支配排序ENS python版
  15. android Alarm闹钟发送广播播放音乐,android适配问题
  16. 计算机设备选型原则,计算机中通信技术的选用应遵循什么原则
  17. 关于 SQL Server Reporting Services 2012 匿名登录
  18. excel窗体最大化最小化_最小化Excel 2007功能区
  19. this.$nextTick
  20. python 循序渐进学习:输出线段图案、正方形图案、直角三角形图案、翻转直角三角形图案、带空格直角三角形图案

热门文章

  1. java说课_Java说课演示稿.ppt
  2. RDD编程初级实践-课程论文
  3. Pomelo PRC
  4. UDK Stat命令
  5. 小鹏 肖志光_自动驾驶:小鹏在展翅,蔚来有未来
  6. VS2019中配置VTK8.2.0
  7. uniApp请确认自定义基座的iOS证书已添加iOS设备的UDID
  8. java构造一个银行账户类
  9. w ndows热键,Windows8 鼠标键盘快捷键热键技巧汇总
  10. 【2023年最新版】渗透测试入门教程,手把手带你进阶渗透测试工程师,学完即可就业