手把手教你分析Mysql死锁问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你分析Mysql死锁问题相关的知识,希望对你有一定的参考价值。

前言

前几天跟一位朋友分析了一个死锁问题,所以有了这篇图文详细的博文,哈哈~手把手教你分析Mysql死锁问题_死锁

发生死锁了,如何排查和解决呢?本文将跟你一起探讨这个问题

  • 准备好数据环境
  • 模拟死锁案发
  • 分析死锁日志
  • 分析死锁结果

环境准备

数据库隔离级别:

  1. mysql> select @@tx_isolation;
  2. +-----------------+
  3. | @@tx_isolation |
  4. +-----------------+
  5. | REPEATABLE-READ |
  6. +-----------------+
  7. 1 row in set, 1 warning (0.00 sec)

自动提交关闭:

  1. mysql> set autocommit=0;
  2. Query OK, 0 rows affected (0.00 sec)

  3. mysql> select @@autocommit;
  4. +--------------+
  5. | @@autocommit |
  6. +--------------+
  7. | 0 |
  8. +--------------+
  9. 1 row in set (0.00 sec)

表结构:

  1. //id是自增主键,name是非唯一索引,balance普通字段
  2. CREATE TABLE `account` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT,
  4. `name` varchar(255) DEFAULT NULL,
  5. `balance` int(11) DEFAULT NULL,
  6. PRIMARY KEY (`id`),
  7. KEY `idx_name` (`name`) USING BTREE
  8. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

表中的数据:手把手教你分析Mysql死锁问题_死锁_02

模拟并发

开启两个终端模拟事务并发情况,执行顺序以及实验现象如下:

手把手教你分析Mysql死锁问题_死锁_03

1)事务A执行更新操作,更新成功

  1. mysql> update account set balance =1000 where name =Wei;
  2. Query OK, 1 row affected (0.01 sec)

2)事务B执行更新操作,更新成功

  1. mysql> update account set balance =1000 where name =Eason;
  2. Query OK, 1 row affected (0.01 sec)

3)事务A执行插入操作,陷入阻塞~

  1. mysql> insert into account values(null,Jay,100);

手把手教你分析Mysql死锁问题_记录锁_04这时候可以用 ​​select*frominformation_schema.innodb_locks;​查看锁情况:

手把手教你分析Mysql死锁问题_死锁_05

4)事务B执行插入操作,插入成功,同时事务A的插入由阻塞变为死锁error。

  1. mysql> insert into account values(null,Yan,100);
  2. Query OK, 1 row affected (0.01 sec)

手把手教你分析Mysql死锁问题_意向锁_06

锁介绍

在分析死锁日志前,先做一下锁介绍,哈哈~

手把手教你分析Mysql死锁问题_意向锁_07主要介绍一下兼容性以及锁模式类型的锁:

共享锁与排他锁

InnoDB 实现了标准的行级锁,包括两种:共享锁(简称 s 锁)、排它锁(简称 x 锁)。

  • 共享锁(S锁):允许持锁事务读取一行。
  • 排他锁(X锁):允许持锁事务更新或者删除一行。

如果事务 T1 持有行 r 的 s 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:

  • T2 请求 s 锁立即被允许,结果 T1 T2 都持有 r 行的 s 锁
  • T2 请求 x 锁不能被立即允许

如果 T1 持有 r 的 x 锁,那么 T2 请求 r 的 x、s 锁都不能被立即允许,T2 必须等待T1释放 x 锁才可以,因为X锁与任何的锁都不兼容。

手把手教你分析Mysql死锁问题_死锁_08

意向锁

  • 意向共享锁( IS 锁):事务想要获得一张表中某几行的共享锁
  • 意向排他锁( IX 锁):事务想要获得一张表中某几行的排他锁

比如:事务1在表1上加了S锁后,事务2想要更改某行记录,需要添加IX锁,由于不兼容,所以需要等待S锁释放;如果事务1在表1上加了IS锁,事务2添加的IX锁与IS锁兼容,就可以操作,这就实现了更细粒度的加锁。

InnoDB存储引擎中锁的兼容性如下表:

手把手教你分析Mysql死锁问题_记录锁_09

记录锁(Record Locks)

  • 记录锁是最简单的行锁,仅仅锁住一行。如: ​SELECT c1 FROM t WHERE c1=10FOR UPDATE
  • 记录锁永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁。
  • 会阻塞其他事务对其插入、更新、删除

记录锁的事务数据(关键词:​lock_mode X locks rec butnotgap​),记录如下:

  1. RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
  2. trx id 10078 lock_mode X locks rec but not gap
  3. Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
  4. 0: len 4; hex 8000000a; asc ;;
  5. 1: len 6; hex 00000000274f; asc O;;
  6. 2: len 7; hex b60000019d0110; asc ;;

间隙锁(Gap Locks)

  • 间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。
  • 使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
  • 间隙锁只阻止其他事务插入到间隙中,他们不阻止其他事务在同一个间隙上获得间隙锁,所以 gap x lock 和 gap s lock 有相同的作用。

间隙锁的事务数据(关键词:​gap before rec​),记录如下:

  1. RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account`
  2. trx id 38049 lock_mode X locks gap before rec
  3. Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
  4. 0: len 3; hex 576569; asc Wei;;
  5. 1: len 4; hex 80000002; asc ;;

Next-Key Locks

  • Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。

插入意向锁(Insert Intention)

  • 插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,亦即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。
  • 假设有索引值4、7,几个不同的事务准备插入5、6,每个锁都在获得插入行的独占锁之前用插入意向锁各自锁住了4、7之间的间隙,但是不阻塞对方因为插入行不冲突。

事务数据类似于下面:

  1. RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
  2. trx id 8731 lock_mode X locks gap before rec insert intention waiting
  3. Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
  4. 0: len 4; hex 80000066; asc f;;
  5. 1: len 6; hex 000000002215; asc " ;;
  6. 2: len 7; hex 9000000172011c; asc r ;;...

锁模式兼容矩阵(横向是已持有锁,纵向是正在请求的锁):

手把手教你分析Mysql死锁问题_记录锁_10

如何读懂死锁日志?

show engine innodb status

可以用 ​show engine innodb status​,查看最近一次死锁日志哈~,执行后,死锁日志如下:

  1. 2020-04-11 00:35:55 0x243c
  2. *** (1) TRANSACTION:
  3. TRANSACTION 38048, ACTIVE 92 sec inserting
  4. mysql tables in use 1, locked 1
  5. LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
  6. MySQL thread id 53, OS thread handle 2300, query id 2362 localhost ::1 root update
  7. insert into account values(null,Jay,100)
  8. *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
  9. RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account`
  10. trx id 38048 lock_mode X locks gap before rec insert intention waiting
  11. Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
  12. 0: len 3; hex 576569; asc Wei;;
  13. 1: len 4; hex 80000002; asc ;;

  14. *** (2) TRANSACTION:
  15. TRANSACTION 38049, ACTIVE 72 sec inserting, thread declared inside InnoDB 5000
  16. mysql tables in use 1, locked 1
  17. 5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
  18. MySQL thread id 52, OS thread handle 9276, query id 2363 localhost ::1 root update
  19. insert into account values(null,Yan,100)
  20. *** (2) HOLDS THE LOCK(S):
  21. RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account`
  22. trx id 38049 lock_mode X locks gap before rec
  23. Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
  24. 0: len 3; hex 576569; asc Wei;;
  25. 1: len 4; hex 80000002; asc ;;

  26. *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
  27. RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account`
  28. trx id 38049 lock_mode X insert intention waiting
  29. Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
  30. 0: len 8; hex 73757072656d756d; asc supremum;;

  31. *** WE ROLL BACK TRANSACTION (1)

我们如何分析以上死锁日志呢?

第一部分

1)找到关键词TRANSACTION,事务38048

手把手教你分析Mysql死锁问题_记录锁_11

2)查看正在执行的SQL

  1. insert into account values(null,Jay,100)

3)正在等待锁释放(WAITING FOR THIS LOCK TO BE GRANTED),插入意向排他锁(lockmode X locks gap before rec insert intention waiting),普通索引(idxname),物理记录(PHYSICAL RECORD),间隙区间(未知,Wei);

手把手教你分析Mysql死锁问题_意向锁_12

第二部分

1)找到关键词TRANSACTION,事务38049

手把手教你分析Mysql死锁问题_记录锁_132)查看正在执行的SQL

  1. insert into account values(null,Yan,100)

3)持有锁(HOLDS THE LOCK),间隙锁(lockmode X locks gap before rec),普通索引(index idxname),物理记录(physical record),区间(未知,Wei);手把手教你分析Mysql死锁问题_记录锁_14

4)正在等待锁释放(waiting for this lock to be granted),插入意向锁(lockmode X insert intention waiting),普通索引上(index idxname),物理记录(physical record),间隙区间(未知,+∞);

以上是关于手把手教你分析Mysql死锁问题的主要内容,如果未能解决你的问题,请参考以下文章

行锁及其算法死锁意向锁

行锁及其算法死锁意向锁

MySQL锁

死锁问题分析,间隙锁

MySQL锁--03---意向锁(Intention Locks)间隙锁(Gap Locks)临键锁(Next-Key Locks)

MYSQL-GAP&插入意向锁 死锁记录