两个小工具,MySQL死锁分析

目录

1. 问题

2. 前置准备

3. 并发事务模拟

4. 实验现象

5. 结果分析

6. 两个工具分析死锁问题

6.1 工具

6.1.1 第一部分

6.1.2 第二部分

6.2 工具二

6.2.1 explain

7. 总结


数据库死锁,是最难调试与追踪的。

1. 问题

图片

同一个表,事务内先插入一条记录,再更新这条记录,并发时会死锁。

图片

并且能够复现。
 

可以通过什么工具模拟并发事务,查看信息,解决问题呢?这是今天要分享的内容。

2. 前置准备

set session transaction isolation level repeatable read;
set session autocommit=0;
 
create table t (
id int(20) primary key AUTO_INCREMENT,
cell varchar(20) unique
)engine=innodb;

start transaction;
insert into t(cell) values(11111111111);
insert into t(cell) values(22222222222);
insert into t(cell) values(33333333333);
commit;

说明:

(1)案发时,事务隔离级别RR;

(2)多终端实验,需要关闭事务自动提交;

(3)建表,设置PK与unique,初始化数据;

3. 并发事务模拟

Session A:
start transaction;
insert into t(cell) values(44444444444);  [1]

            Session B:
            start transaction;
            insert into t(cell) values(55555555555); [2]
update t set cell=123 where cell=44444444444; [3]   
            update t set cell=456 where cell=55555555555; [4]

开启两个终端模拟并发事务:

(1)位置靠前的SQL为事务A;

(2)位置靠后的SQL为事务B;

(3)[1][2][3][4]为执行时序;

4. 实验现象

insert into t(cell)values(44444444444);  [1]

事务A插入数据,最先执行

结果:插入成功

 

insert into t(cell) values(55555555555); [2]

事务B插入数据,第二执行

结果:插入成功

 

update t set cell=123 where cell=44444444444; [3]

事务A修改[1]中插入的数据,第三执行

结果:阻塞,等待执行结果

图片

画外音:修改一条自己插入的数据,在等待什么呢?

 

update t set cell=456 where cell=55555555555; [4]

事务B修改[2]中插入的数据,最后执行结果:

(1)事务B死锁,事务B被回滚;

图片

(2)事务A中,[3]语句阻塞结束,执行成功;

图片

画外音:说明事务A中阻塞的语句,确实在等事务B中的某个锁。

5. 结果分析

两个事务,各自修改自己插入的数据,却产生了死锁,确实诡异。

 

上述实验现象的两个核心问题是:

(1)语句[3]阻塞,在等待什么锁?

(2)语句[4]死锁,此时事务A和事务B一定是彼此占住一把锁,请求彼此的锁,这些锁又是什么呢?

6. 两个工具分析死锁问题

6.1 工具一

show engine innodb status;

画外音:前文《超赞,InnoDB调试死锁的方法!》就详细分享过,InnoDB死锁的分析实践。

执行之后,显示的内容如下(放大仔细看):

图片

信息很多,别急,楼主娓娓道来。

6.1.1 第一部分

关键词是:

(1)Transaction 1,事务3998;

(2)在执行

update t set cell=123 where cell=44444444444;

(3)正在等待锁释放(waiting for this lock to be granted),记录锁(record locks),主键索引上(index primary),互斥锁(lock_mode X),物理记录(physical record),asc 55555555555;

画外音:英文比较差没事,抓关键词。

 

画外音,InnoDB存储引擎,聚集索引与非聚集索引的实现方式,决定了锁会加在聚集索引上,详见文章:

1分钟了解MyISAM与InnoDB的索引差异》。

6.1.2 第二部分

关键词是:

(1)Transaction 2,事务3999;

(2)正在执行

update t set cell=456 where cell=55555555555;

(3)持有锁(holds the lock),记录锁(record locks),主键索引上(index primary),互斥锁(lock_mode X),物理记录(physical record),asc 55555555555;

(4)正在等待锁释放(waiting for this lock to be granted),记录锁(record locks),主键索引上(index primary),互斥锁(lock_mode X),物理记录(physical record),asc 11111111111;

(5)事务2回滚(we roll back transaction 2);

 

通过show engine innodb status; 能够看到很多事务与锁之间的信息,对分析问题十分有帮助,这些信息,能够解释一些问题,但仍有两个疑惑:(其实写完了文章,还是不太明白)

(1)事务1为啥想拿55555555555的锁?

画外音:这正是,事务1被阻塞的原因。

 

(2)事务2为啥想拿11111111111的锁?死锁的发生,说明事务1此时真占着11111111111的锁,这又是为什么呢?

画外音:第一个事务占111抢555,第二个事务占555抢111,循环嵌套,才会死锁。

 

6.2 工具二

6.2.1 explain

为了进一步寻找原因,可以通过explain看下导致死锁语句的执行计划。

explain update t set cell=456 where cell=55555555555;

图片

(1)select_type:SIMPLE

这是一个简单类型的SQL语句,不含子查询或者UNION。

(2)type:index

访问类型,即找到所需数据使用的遍历方式,潜在的方式有:

(2.1)ALL(Full Table Scan):全表扫描;

(2.2)index:走索引的全表扫描;

(2.3)range:命中where子句的范围索引扫描;

(2.4)ref/eq_ref:非唯一索引/唯一索引单值扫描;

(2.5)const/system:常量扫描;

(2.6)NULL:不用访问表;

 

上述扫描方式,ALL最慢,逐步变快,NULL最快。

 

怀疑点1:明明cell字段有uniq索引,为何要进行走PK索引的全表扫描呢?

(3)possible_keys:NULL

可能在哪个索引找到记录。

 

(4)key:PRIMARY

实际使用索引。

画外音:使用PK进行的全表扫描。

 

(5)ref:NULL

哪些列,或者常量用于查找索引上的值。

 

怀疑点2:where条件中的查询条件55555555555,本来应该作为在索引上被检索的值呀?

 

(6)rows:5

找到所需记录,预估需要读取的行数。

 

怀疑点3:明明修改的是5,为何初始化的1,2,3,以及第一个事务插入的4,以及第二个事务插入的5,都要被读取呢?不应该全表扫描呀。

 

通过explain,基本已经可以判断:

update t set cell=456 where cell=55555555555;

并没有和我们预想一样,走cell索引进行查询,而是走了PK索引进行了全表扫描

 

再仔细一看:

create table t (
   id int(20) primary key AUTO_INCREMENT,
   cell varchar(20) unique
)engine=innodb;

建表的时候cell定义的是字符串类型。

 

更新的时候,

update t set cell=456 where cell=55555555555;

使用的是整数类型。

 

类型转换,会导致全表扫描,出现锁升级,锁住全部记录。

 

加上引号,再次通过explain验证一下:

explain update t set cell= '456 ' where cell= '55555555555 ';

图片

果然印证了猜想:

(1)type:range,变为了走索引的字符串比对,范围扫描;

(2)possible_keys:cell,通过cell索引找到了记录;

(3)key:cell,实际使用cell索引;

(4)ref:const,使用了常量' 555'进行比对;

(5)rows:1,预估读取行数是1;

 

这下全部可以解释了。

图片

7. 总结

就本例而言:需要注意字符串与整数之间的强制类型转换,有时候少一个引号,就会使得行锁升级为表锁。

 

死锁是MySQL中非常难调试的问题,常见的思路与方法有:

(1)通过多终端模拟并发事务,复现死锁;

(2)通过show engine innodb status; 可以查看事务与锁的信息;

(3)通过explain可以查看执行计划;

 

思路比结论更重要,希望大家有收获。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值