帮一位朋友分析了一个死锁问题,手把手教L死锁问所以有了这篇图文详细的分析博文,非常详细,手把手教L死锁问建议收藏起来慢慢研究:
图片
发生死锁了,分析如何排查和解决呢?手把手教L死锁问本文将跟你一起探讨这个问题
准备好数据环境模拟死锁案发分析死锁日志分析死锁结果数据库隔离级别:
复制mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set, 1 warning (0.00 sec)1.2.3.4.5.6.7.自动提交关闭:
复制mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec)1.2.3.4.5.6.7.8.9.表结构:
复制//id是自增主键,name是分析非唯一索引,balance普通字段 CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT,手把手教L死锁问 `name` varchar(255) DEFAULT NULL, `balance` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;1.2.3.4.5.6.7.8.表中的数据:
图片
开启两个终端模拟事务并发情况,执行顺序以及实验现象如下:
图片

1)事务A执行更新操作,分析更新成功
复制mysql> update account set balance =1000 where name =Wei; Query OK,手把手教L死锁问 1 row affected (0.01 sec)1.2.2)事务B执行更新操作,更新成功
复制mysql> update account set balance =1000 where name =Eason; Query OK,分析 1 row affected (0.01 sec)1.2.3)事务A执行插入操作,陷入阻塞~
复制mysql> insert into account values(null,手把手教L死锁问Jay,100);1.
图片
这时候可以用 select*frominformation_schema.innodb_locks;查看锁情况:
图片
4)事务B执行插入操作,插入成功,分析同时事务A的手把手教L死锁问插入由阻塞变为死锁error。
复制mysql> insert into account values(null,分析Yan,100); Query OK, 1 row affected (0.01 sec)1.2.
图片
在分析死锁日志前,先做一下锁介绍,手把手教L死锁问哈哈~
图片
主要介绍一下兼容性以及锁模式类型的锁:
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锁与任何的锁都不兼容。
图片
比如:事务1在表1上加了S锁后,事务2想要更改某行记录,需要添加IX锁,由于不兼容,所以需要等待S锁释放;如果事务1在表1上加了IS锁,事务2添加的IX锁与IS锁兼容,就可以操作,这就实现了更细粒度的加锁。
InnoDB存储引擎中锁的兼容性如下表:
图片
记录锁的事务数据(关键词:lock_mode X locks rec butnotgap),记录如下:
复制RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` trx id 10078 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 6; hex 00000000274f; asc O;; 2: len 7; hex b60000019d0110; asc ;;1.2.3.4.5.6.间隙锁的事务数据(关键词:gap before rec),记录如下:
复制RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38049 lock_mode X locks gap before rec Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 3; hex 576569; asc Wei;; 1: len 4; hex 80000002; asc ;;1.2.3.4.5.事务数据类似于下面:
复制RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child` trx id 8731 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000066; asc f;; 1: len 6; hex 000000002215; asc " ;; 2: len 7; hex 9000000172011c; asc r ;;...1.2.3.4.5.6.锁模式兼容矩阵(横向是已持有锁,纵向是正在请求的锁):
图片
可以用 show engine innodb status,查看最近一次死锁日志哈~,执行后,死锁日志如下:
复制2020-04-11 00:35:55 0x243c *** (1) TRANSACTION: TRANSACTION 38048, ACTIVE 92 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2 MySQL thread id 53, OS thread handle 2300, query id 2362 localhost ::1 root update insert into account values(null,Jay,100) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38048 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 3; hex 576569; asc Wei;; 1: len 4; hex 80000002; asc ;; *** (2) TRANSACTION: TRANSACTION 38049, ACTIVE 72 sec inserting, thread declared inside InnoDB 5000 mysql tables in use 1, locked 1 5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2 MySQL thread id 52, OS thread handle 9276, query id 2363 localhost ::1 root update insert into account values(null,Yan,100) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38049 lock_mode X locks gap before rec Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 3; hex 576569; asc Wei;; 1: len 4; hex 80000002; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account` trx id 38049 lock_mode X insert intention waiting Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** WE ROLL BACK TRANSACTION (1)1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.我们如何分析以上死锁日志呢?
1)找到关键词TRANSACTION,事务38048
图片
2)查看正在执行的SQL
复制insert into account values(null,Jay,100)1.3)正在等待锁释放(WAITING FOR THIS LOCK TO BE GRANTED),插入意向排他锁(lockmode X locks gap before rec insert intention waiting),普通索引(idxname),物理记录(PHYSICAL RECORD),间隙区间(未知,Wei);
图片
1)找到关键词TRANSACTION,事务38049
图片
2)查看正在执行的SQL
复制insert into account values(null,Yan,100)1.3)持有锁(HOLDS THE LOCK),间隙锁(lockmode X locks gap before rec),普通索引(index idxname),物理记录(physical record),区间(未知,Wei);
图片
4)正在等待锁释放(waiting for this lock to be granted),插入意向锁(lockmode X insert intention waiting),普通索引上(index idxname),物理记录(physical record),间隙区间(未知,+∞);
图片
5)事务1回滚(we roll back transaction 1);
图片
查看日志可得:
事务A正在等待的插入意向排他锁(事务A即日志的事务1,根据insert语句来对号入座的哈),正在事务B的怀里~事务B持有间隙锁,正在等待插入意向排它锁这里面,有些朋友可能有疑惑,
事务A持有什么锁呢?日志根本看不出来。它又想拿什么样的插入意向排他锁呢?事务B拿了具体什么的间隙锁呢?它为什么也要拿插入意向锁?死锁的死循环是怎么形成的?目前日志看不出死循环构成呢?我们接下来一小节详细分析一波,一个一个问题来~
图片
为了方便记录,例子用W表示Wei,J表示Jay,E表示Eason哈~
我们先来分析事务A中update语句的加锁情况~ 复制update account set balance =1000 where name =Wei;1.间隙锁:
Update语句会在非唯一索引的name加上左区间的间隙锁,右区间的间隙锁(因为目前表中只有name=Wei的一条记录,所以没有中间的间隙锁~),即(E,W) 和(W,+∞)为什么存在间隙锁?因为这是RR的数据库隔离级别,用来解决幻读问题用的~记录锁
因为name是索引,所以该update语句肯定会加上W的记录锁Next-Key锁
Next-Key锁=记录锁+间隙锁,所以该update语句就有了(E,W]的 Next-Key锁综上所述,事务A执行完update更新语句,会持有锁:
Next-key Lock:(E,W]Gap Lock :(W,+∞)我们再来分析一波事务A中insert语句的加锁情况 复制insert into account values(null,Jay,100);1.间隙锁:
因为Jay(J在E和W之间),所以需要请求加(E,W)的间隙锁插入意向锁(Insert Intention)
插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即事务A需要插入意向锁(E,W)因此,事务A的update语句和insert语句执行完,它是持有了 (E,W]的 Next-Key锁,(W,+∞)的Gap锁,想拿到 (E,W)的插入意向排它锁,等待的锁跟死锁日志是对上的,哈哈~
图片
间隙锁:
Update语句会在非唯一索引的name加上左区间的间隙锁,右区间的间隙锁(因为目前表中只有name=Eason的一条记录,所以没有中间的间隙锁~),即(-∞,E)和(E,W)记录锁
因为name是索引,所以该update语句肯定会加上E的记录锁Next-Key锁
Next-Key锁=记录锁+间隙锁,所以该Update语句就有了(-∞,E]的 Next-Key锁综上所述,事务B执行完update更新语句,会持有锁:
Next-key Lock:(-∞,E]Gap Lock :(E,W)我们再来分析一波B中insert语句的加锁情况 复制insert into account values(null,Yan,100);1.间隙锁:
因为Yan(Y在W之后),所以需要请求加(W,+∞)的间隙锁插入意向锁(Insert Intention)
插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即事务A需要插入意向锁(W,+∞)所以,事务B的update语句和insert语句执行完,它是持有了 (-∞,E]的 Next-Key锁,(E,W)的Gap锁,想拿到 (W,+∞)的间隙锁,即插入意向排它锁,加锁情况跟死锁日志也是对上的~
图片

接下来呢,让我们一起还原死锁真相吧~哈哈~
图片
最后,遇到死锁问题,我们应该怎么分析呢?
模拟死锁场景show engine innodb status;查看死锁日志找出死锁SQLSQL加锁分析,这个可以去官网看哈分析死锁日志(持有什么锁,等待什么锁)熟悉锁模式兼容矩阵,InnoDB存储引擎中锁的兼容性矩阵。(责任编辑:应用开发)