[译文]MySQL发生死锁肿么办?by何金龙

一、导读

在 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”

时间: 2024-09-17 03:48:59

[译文]MySQL发生死锁肿么办?by何金龙的相关文章

两个INSERT发生死锁原因剖析

两个INSERT也能发生死锁?貌似不可思议,实际上是正常的. 本文整理过程中,先后向高鹏.王少华.苏斌等几位朋友请教确认,感谢. 开始之前,关于锁.死锁,我们要先统一下几点认知: 死锁是由于多个事务相互持有其他事务所需要的锁,结果导致事务都无法继续,进而触发死锁检测,其中某个事务会被回滚,释放相应的锁,其他事务得以正常继续:简言之,就是多个事务之间的锁等待产生了回路,死循环了: 死锁发生时,会立刻被检测到,并且回滚其中某个事务,而不会长时间阻塞.等待: 从MySQL 5.7.15开始,新增选项

MySQL更新死锁问题

作为一个社交类的 App ,我们有很多操作都会同时发生,为了确保数据的一致性,会采用数据库的事物. 比如现在我们有一个点赞操作,点赞成功后,需要更改文章的热度.以下是 SQL 语句: INSERT INTO user_praise(uid,plan_id,stage_id) VALUES(123456,14456,10023); UPDATE plan_hot SET hot = hot + 1 WHERE plan_id = 14456; 在这里我们需要用到事物来确保它的原子性,也就是要么这两

【MySQL】死锁案例之三

一 前言       死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA和部分开发朋友都会在工作过程中遇见过.关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助.二 背景知识2.1 insert 锁机制 在分析死锁案例之前,我们先学习一下背景知识 insert 语句的加锁策略.我们先来看看官方定义: "An insert intention lock is a type of gap lock set by INSERT operations prior to

图标变白无法打开-江湖救急,win8.1桌面图标消失,任务栏的应用图标变白且无法打开,该肿么办,求大神解救,拜谢。

问题描述 江湖救急,win8.1桌面图标消失,任务栏的应用图标变白且无法打开,该肿么办,求大神解救,拜谢. 江湖救急,我的笔电是win8.1的系统,桌面图标消失,任务栏的应用图标变白且无法打开,该肿么办,求大神解救,除了重装系统外,还有其他办法让电脑恢复正常吗?拜谢... 解决方案 用mhdd扫描下,硬盘可能有坏道.建议更换硬盘. 解决方案二: 用安装盘修复一下系统试试.

jbpm4.4 部署时,发生死锁问题

问题描述 jbpm4.4 部署时,发生死锁问题 jbpm中deploy的时候出现死锁问题. 发现数据库中以下三个表被锁定 JBPM4_LOB JBPM4_DEPLOYMENT JBPM4_DEPLOYPROP 然后控制台不断重复打印下面的sql语句,像死循环一样,求解. 网上说加上索引就好了,不知道具体在哪张表哪个字段上加索引呢?求解 Hibernate: select deployment0_.dbid_ as dbid1_43_0_, deployment0_.name_ as name2_

MySQL发生错误:The user specified as a definer (XXXXXX@XXX) does not exist

问题描述 MySQL发生错误:The user specified as a definer (XXXXXX@XXX) does not exist不知道是什么问题 解决方案 意思是某个数据库对象的定义者(也就是xxxxxx@xx)不存在所以只要将视图的定义者修改为你自己用的用户名即可

使用局域网IP连接服务器变成用公网IP连接,肿么办?

问题描述 使用局域网IP连接服务器变成用公网IP连接,肿么办? 有两台JBOSS服务器发布工程在内网,它们也有对应的外网IP,有一台Apache服务器反向代理负载那两台服务器的服务发布在外网,在一台JBOSS服务器端创建连接另外一台服务器端的Context,使用局域网IP进行的连接,但是在连接时却发现连接不上(外网连的端口被禁),使用拦截器发现它的连接竟然变成了外网IP,请问这个是什么原因造成的呢?这个要怎么解决呢? 解决方案 没看明白 解决方案二: 是否设置了端口转发等策略? 解决方案三: 是

基带损坏了,又不会修,肿么办?

问题描述 基带损坏了,又不会修,肿么办? 基带损坏了,不能识别sim卡,机型huawei c8825d前几天刷了个百度云的包,现在不想强刷,求帮忙 解决方案 这种垃圾手机,也就是百把块钱的价值,扔了再买一个就是了.

java-android中的xml文件的spinner标签的prompt属性出错肿么办

问题描述 android中的xml文件的spinner标签的prompt属性出错肿么办 android中的xml文件的spinner标签的prompt属性出错肿么办 prompt资源来自哪里呢? 解决方案 prompt属性不是自己设的吗?怎么会出错?