原标题:手把手教你分析 MySQL 死锁问题

前言

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

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

准备好数据环境

模拟死锁案发

分析死锁日志

分析死锁结果

环境准备

数据库隔离级别:

mysql> select @ @tx_isolation ;

+-----------------+

| @ @tx_isolation |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row inset , 1 warning ( 0.00 sec)

自动提交关闭:

mysql> set autocommit= 0 ;

Query OK, 0 rows affected ( 0.00 sec)

mysql> select @ @autocommit ;

+--------------+

| @ @autocommit |

+--------------+

| 0 |

+--------------+

1 row inset ( 0.00 sec)

表结构:

//id是自增主键,name是非唯一索引,balance普通字段

CREATE TABLE `account` (

`id` int ( 11 ) NOT NULL AUTO_INCREMENT,

`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)事务A执行更新操作,更新成功

mysql> update account set balance = 1000 where name = 'Wei' ;

Query OK, 1 row affected ( 0.01 sec)

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

mysql> update account set balance = 1000 where name = 'Eason' ;

Query OK, 1 row affected ( 0.01 sec)

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

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

这时候可以用select*frominformation_schema.innodb_locks;查看锁情况:

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

mysql> insert into account values( null , 'Yan' , 100 );

Query OK, 1 row affected ( 0.01 sec)

锁介绍

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

主要介绍一下兼容性以及锁模式类型的锁:

共享锁与排他锁

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锁与任何的锁都不兼容。

意向锁

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

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

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

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

记录锁(Record Locks)

记录锁是最简单的行锁, 仅仅锁住一行 。如: SELECT c1 FROM t WHERE c1=10FOR UPDATE

记录锁 永远都是加在索引上 的,即使一个表没有索引,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

1 : len 6 ; hex 00000000274f ; asc 'O;;

间隙锁(Gap Locks)

间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。

使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

间隙锁只阻止其他事务插入到间隙中,他们不阻止其他事务在同一个间隙上获得间隙锁,所以 gap x lock 和 gap s lock 有相同的作用。

间隙锁的事务数据(关键词: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

Next-Key Locks

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

插入意向锁(Insert Intention)

插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,亦即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。

假设有索引值4、7,几个不同的事务准备插入5、6,每个锁都在获得插入行的独占锁之前用插入意向锁各自锁住了4、7之间的间隙,但是不阻塞对方因为插入行不冲突。

事务数据类似于下面:

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

1 : len 6 ; hex 000000002215 ; asc " ;;

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

如何读懂死锁日志?show engine innodb status

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

2020 - 04 - 1100 : 35 : 550x243c

*** ( 1 ) TRANSACTION:

TRANSACTION 38048 , ACTIVE 92 sec inserting

mysql tables inuse 1 , locked 1

LOCK WAIT 4 lockstruct (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

*** ( 2 ) TRANSACTION:

TRANSACTION 38049 , ACTIVE 72 sec inserting, thread declared inside InnoDB 5000

mysql tables inuse 1 , locked 1

5 lockstruct (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

*** ( 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

*** WE ROLL BACK TRANSACTION ( 1 )

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

第一部分

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

2)查看正在执行的SQL

insert into account values( null , 'Jay' , 100 )

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

第二部分

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

2)查看正在执行的SQL

insert into account values( null , 'Yan' , 100 )

3)持有锁(HOLDS THE LOCK),间隙锁(lock mode X locks gap before rec),普通索引(index idxname),物理记录(physical record),区间(未知,Wei);

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

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

查看日志结果

查看日志可得:

事务A正在等待的插入意向排他锁(事务A即日志的事务1,根据insert语句来对号入座的哈),正在事务B的怀里~

事务B持有间隙锁,正在等待插入意向排它锁

这里面,有些朋友可能有 疑惑,

事务A持有什么锁呢?日志根本看不出来。它又想拿什么样的插入意向排他锁呢?

事务B拿了具体什么的间隙锁呢?它为什么也要拿插入意向锁?

死锁的死循环是怎么形成的?目前日志看不出死循环构成呢?

我们接下来一小节详细分析一波,一个一个问题来~

死锁分析死锁死循环四要素

互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

事务A持有什么锁呢?它又想拿什么样的插入意向排他锁呢?

为了方便记录,例子用W表示Wei,J表示Jay,E表示Eason哈~

我们先来分析事务A中update语句的加锁情况~

update account set balance = 1000 where name = 'Wei' ;

间隙锁:

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 );

间隙锁:

因为Jay(J在E和W之间),所以需要请求加(E,W)的间隙锁

插入意向锁(Insert Intention)

插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即事务A需要插入意向锁(E,W)

因此,事务A的update语句和insert语句执行完,它是持有了 (E,W]的 Next-Key锁, (W,+∞)的Gap锁,想拿到 (E,W)的插入意向排它锁,等待的锁跟死锁日志是对上的,哈哈~

事务B拥有了什么间隙锁?它为什么也要拿插入意向锁?同理,我们再来分析一波事务B,update语句的加锁分析:

update account set balance = 1000 where name = 'Eason' ;

间隙锁:

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 );

间隙锁:

因为Yan(Y在W之后),所以需要请求加(W,+∞)的间隙锁

插入意向锁(Insert Intention)

插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即事务A需要插入意向锁(W,+∞)

所以,事务B的update语句和insert语句执行完,它是持有了 (-∞,E]的 Next-Key锁, (E,W)的Gap锁,想拿到 (W,+∞)的间隙锁,即插入意向排它锁,加锁情况跟死锁日志也是对上的~

死锁真相还原

接下来呢,让我们一起还原死锁真相吧~哈哈~

事务A执行完Update Wei的语句,持有(E,W]的Next-key Lock,(W,+∞)的Gap Lock ,插入成功~

事务B执行完Update Eason语句,持有(-∞,E]的 Next-Key Lock,(E,W)的Gap Lock,插入成功~

事务A执行Insert Jay的语句时,因为需要(E,W)的插入意向锁,但是(E,W)在事务B怀里,所以它陷入心塞~

事务B执行Insert Yan的语句时,因为需要(W,+∞) 的插入意向锁,但是(W,+∞) 在事务A怀里,所以它也陷入心塞。

事务A持有(W,+∞)的Gap Lock,在等待(E,W)的插入意向锁,事务B持有(E,W)的Gap锁,在等待(W,+∞) 的插入意向锁,所以形成了死锁的闭环~(Gap锁与插入意向锁会冲突的,可以看回锁介绍的锁模式兼容矩阵哈~)

事务A,B形成了死锁闭环后,因为Innodb的底层机制,它会让其中一个事务让出资源,另外的事务执行成功,这就是为什么你最后看到事务B插入成功了,但是事务A的插入显示了Deadlock found ~

总结

最后,遇到死锁问题,我们应该怎么分析呢?返回搜狐,查看更多

模拟死锁场景

show engine innodb status;查看死锁日志

找出死锁SQL

SQL加锁分析,这个可以去官网看哈

分析死锁日志(持有什么锁,等待什么锁)

熟悉锁模式兼容矩阵,InnoDB存储引擎中锁的兼容性矩阵。

责任编辑:

mysql排插问题_手把手教你分析 MySQL 死锁问题相关推荐

  1. mysql中括号_手把手教你看MySQL官方文档

    前言: 在学习和使用MySQL的过程中,难免会遇到各种问题.不知道当你遇到相关问题时会怎么做,我在工作或写文章的过程中,遇到不懂或需要求证的问题时通常会去查阅官方文档.慢慢的,阅读文档也有了一些经验, ...

  2. mysql官方文档中文版_手把手教你看MySQL官方文档

    前言: 在学习和使用MySQL的过程中,难免会遇到各种问题.不知道当你遇到相关问题时会怎么做,我在工作或写文章的过程中,遇到不懂或需要求证的问题时通常会去查阅官方文档.慢慢的,阅读文档也有了一些经验, ...

  3. linux按照mysql为何如此简单_手把手教你在Linux下安装MySQL

    在Linux操作系统下,安装MYSQL有两种方式:一种tar安装方式,另外一种是rpm安装方式.这两种安装方式有什么区别呢?尽管我们在Linux下常用tar来压缩/解压缩文件,但MYSQL的tar格式 ...

  4. mysql 查看autocommit_手把手教你分析Mysql死锁问题

    点击上方 IT牧场 ,选择 置顶或者星标 技术干货每日送达 发生死锁了,如何排查和解决呢?本文将跟你一起探讨这个问题 准备好数据环境 模拟死锁案发 分析死锁日志 分析死锁结果 环境准备 数据库隔离级别 ...

  5. 手把手教你分析MySQL死锁问题,十分钟看完文章下次轻松完成不加班

    发生死锁了,如何排查和解决呢?本文将跟你一起探讨这个问题 准备好数据环境 模拟死锁案发 分析死锁日志 分析死锁结果 环境准备 数据库隔离级别: mysql> select @@tx_isolat ...

  6. MySQL超详细安装教程 手把手教你安装MySQL到使用MySQL 最简单的MySQL安装方式,这种方式装,卸载也简单(安装mysql的步骤和方法)

    目录 MySQL 压缩包下载地址: 下载方式: 安装步骤: 一.解压下载的文件: 二.给解压的文件改名 三.将这个包放到自己想要存放的电脑目录下 四.配置环境变量 1.右键我的电脑,选择属性 2.选择 ...

  7. MySQL超详细安装教程 手把手教你安装MySQL到使用MySQL 最简单的MySQL安装方式,这种方式装,卸载也简单

    目录 MySQL 压缩包下载地址:? 下载方式: 安装步骤: 一.解压下载的文件: 二.给解压的文件改名? ?三.将这个包放到自己想要存放的电脑目录下 ?四.配置环境变量 1.右键我的电脑,选择属性 ...

  8. python基金比较上机题_手把手教你用python选基金

    买基金是上班族用零钱进行投资的正确姿势.而自己用数据来选基金比听别人推荐买什么基金要好上一百倍. 步骤如下: 1.获取网上的基金的排名信息,使用四四三三法则筛选出排名靠前的基金.2.获取网上的基金的基 ...

  9. windbg分析dmp蓝屏文件_手把手教你分析漏洞 : CVE-2018-8120

    实验环境 双虚拟机调试 这里我把漏洞机和调试机都放在了虚拟机里,采用虚拟机调试虚拟机的好处就是可以实时保存快照,有空的时候在接着工作(其实是为了更好的加班,开个玩笑~~ 1.漏洞机的设置 在虚拟机里安 ...

最新文章

  1. EOS主网上线只是开始,如何运营决定未来
  2. mysql字段值后面有隐形字符_MySQL 隐形索引
  3. android 图片放大缩小 多点触摸,Android 多点触摸(图片放大缩小)
  4. 基于Linux的嵌入式浏览器的实现
  5. soureTree中如何设置git 用户名与密码 SourceTree提交修改用户详细图文方法
  6. php安装dat,PHP Parsing a .dat file
  7. 均线交易策略的回测 r_使用r创建交易策略并进行回测
  8. 从编译到执行,C++如何开发SIMD友好的代码?
  9. python坐标定位_如何利用Python识别并定位图片中某一个色块的坐标?
  10. python set集合_Python字典(dict)和集合(set)
  11. 写在弥勒宝贝两周年之际
  12. 第15届创新英语大赛初赛第二阶段题目
  13. vue 自定义打印CLodop
  14. 阿里云短信接口配置教程
  15. “蔚小理”想挑战特斯拉?先干过比亚迪再说
  16. Linux命令之压缩gzip
  17. 从键盘上输入以下的数据:TOM:89|JERRY:90|TONY:95,数据格式为“姓名:成绩|姓名:成绩|姓名:成绩”,对输入的内容按成绩进行排序,并将结果按成绩由高到低排序。
  18. 考试试卷用什么纸打印,哪里打印试卷便宜
  19. Python经典例题:跑马灯文字效应
  20. Home Assistant设备追踪之ping检测和nmap检测

热门文章

  1. OpenShift 4 - 锁定被保护的 OpenShift 资源,禁止删除和修改操作
  2. C# 用IrisSkin4.dll美化你的WinForm
  3. Visual Studio Code 10 月 Python 扩展更新
  4. php webp decode.h,HCTF两道web题目
  5. zookeeper和eureka的对比
  6. 为什么不能线程调用类的成员函数_C++多线程编程之创建线程的几种方法
  7. 乐高科技系列搭建指南 pdf_玩转乐高创意亲子搭建系列(4)小颗粒作品
  8. 国产plc做modbus从站_Modbus-RTU通信
  9. 头部导航菜单选中状态切换
  10. ionic4 QQ登陆集成