之前收到读者面试字节时,被问到一个关于 MySQL 的问题。

如果对 MySQL 加锁机制比较熟悉的同学,应该一眼就能看出会发生死锁。

但是具体加了什么锁而导致死锁,是需要我们具体分析的。

接下来,就跟聊聊上面两个事务执行 SQL 语句的过程中,加了什么锁,从而导致死锁的。

准备工作

先创建一张 t_student 表,假设除了 id 字段,其他字段都是普通字段。

CREATE  TABLE  `t_student` (
   `id`  int  NOT  NULL,
   `no`  varchar( 255)  DEFAULT  NULL,
   `name`  varchar( 255)  DEFAULT  NULL,
   `age`  int  DEFAULT  NULL,
   `score`  int  DEFAULT  NULL,
  PRIMARY  KEY ( `id`)
)  ENGINE= InnoDB  DEFAULT  CHARSET=utf8mb4;

然后,插入相关的数据后,t_student 表中的记录如下:

开始实验

在实验开始前,先说明下实验环境:

  • MySQL 版本:8.0.26
  • 隔离级别:可重复读(RR)

启动两个事务,按照题目的 SQL 执行顺序,过程如下表格:

可以看到,事务 A 和 事务 B 都在执行  insert 语句后,都陷入了等待状态(前提没有打开死锁检测),也就是发生了死锁,因为都在相互等待对方释放锁。

为什么会发生死锁?

我们可以通过 select * from performance_schema.data_locks\G; 这条语句,查看事务执行 SQL 过程中加了什么锁。

接下来,针对每一条 SQL 语句分析具体加了什么锁。

Time 1 阶段加锁分析

Time 1 阶段,事务 A 执行以下语句:

# 事务 A
mysql>  begin;
Query OK, 0 rows affected (0.00 sec)

mysql>  update t_student  set score =  100  where  id =  25;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 0  Changed: 0  Warnings: 0

然后执行 select * from performance_schema.data_locks\G; 这条语句,查看事务 A 此时加了什么锁。

从上图可以看到,共加了两个锁,分别是:

  • 表锁:X 类型的意向锁;
  • 行锁:X 类型的间隙锁;

这里我们重点关注行锁,图中 LOCK_TYPE 中的 RECORD 表示行级锁,而不是记录锁的意思,通过 LOCK_MODE 可以确认是 next-key 锁,还是间隙锁,还是记录锁:

  • 如果 LOCK_MODE 为 X,说明是 next-key 锁;
  • 如果 LOCK_MODE 为 X, REC_NOT_GAP,说明是记录锁;
  • 如果 LOCK_MODE 为 X, GAP,说明是间隙锁;

因此,此时事务 A 在主键索引(INDEX_NAME : PRIMARY)上加的是间隙锁,锁范围是(20, 30)。

Time 2 阶段加锁分析

Time 2 阶段,事务 B 执行以下语句:

# 事务 B
mysql>  begin;
Query OK, 0 rows affected (0.00 sec)

mysql>  update t_student  set score =  100  where  id =  26;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 0  Changed: 0  Warnings: 0

然后执行 select * from performance_schema.data_locks\G; 这条语句,查看事务 B 此时加了什么锁。

从上图可以看到,共加了两个锁,分别是:

  • 表锁:X 类型的意向锁;
  • 行锁:X 类型的间隙锁;

因此,此时事务 B 在主键索引(INDEX_NAME : PRIMARY)上加的是间隙锁,锁范围是(20, 30)。

事务 A 和 事务 B 的间隙锁范围都是一样的,为什么不会冲突?

两个事务的间隙锁之间是相互兼容的,不会产生冲突。

在MySQL官网上还有一段非常关键的描述:

Gap locks in InnoDB are “purely inhibitive”, which means that their only purpose is to prevent other transactions from Inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.

间隙锁的意义只在于阻止区间被插入,因此是可以共存的。一个事务获取的间隙锁不会阻止另一个事务获取同一个间隙范围的间隙锁,共享和排他的间隙锁是没有区别的,他们相互不冲突,且功能相同。

Time 3 阶段加锁分析

Time 3,事务 A 插入了一条记录:

# Time 3 阶段,事务 A 插入了一条记录
mysql>  insert  into t_student( id,  no,  name, age,score)  value ( 25,  'S0025',  'sony',  28,  90);
    /// 阻塞等待......

此时,事务 A 就陷入了等待状态。

然后执行 select * from performance_schema.data_locks\G; 这条语句,查看事务 A 在获取什么锁而导致被阻塞。

可以看到,事务 A 的状态为等待状态(LOCK_STATUS: WAITING),因为向事务 B 生成的间隙锁(范围 (20, 30))中插入了一条记录,所以事务 A 的插入操作生成了一个插入意向锁(LOCK_MODE:INSERT_INTENTION)。

插入意向锁是什么?

注意!插入意向锁名字里虽然有意向锁这三个字,但是它并不是意向锁,它属于行级锁,是一种特殊的间隙锁。

在MySQL的官方文档中有以下重要描述:

An Insert intention lock is a type of gap lock set by Insert operations prior to row Insertion. This lock signals the intent to Insert in such a way that multiple transactions Inserting into the same index gap need not wait for each other if they are not Inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to Insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with Insert intention locks prior to obtaining the exclusive lock on the Inserted row, but do not block each other because the rows are nonconflicting.

这段话表明尽管插入意向锁是一种特殊的间隙锁,但不同于间隙锁的是,该锁只用于并发插入操作

如果说间隙锁锁住的是一个区间,那么「插入意向锁」锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。

插入意向锁与间隙锁的另一个非常重要的差别是:尽管「插入意向锁」也属于间隙锁,但两个事务却不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。所以,插入意向锁和间隙锁之间是冲突的

另外,我补充一点,插入意向锁的生成时机:

  • 每插入一条新记录,都需要看一下待插入记录的下一条记录上是否已经被加了间隙锁,如果已加间隙锁,那 Insert 语句会被阻塞,并生成一个插入意向锁 。

Time 4 阶段加锁分析

Time 4,事务 B 插入了一条记录:

# Time 4 阶段,事务 B 插入了一条记录
mysql>  insert  into t_student( id,  no,  name, age,score)  value ( 26,  'S0026',  'ace',  28,  90);
    /// 阻塞等待......

此时,事务 B 就陷入了等待状态。

然后执行 select * from performance_schema.data_locks\G; 这条语句,查看事务 B 在获取什么锁而导致被阻塞。

可以看到,事务 B 在生成插入意向锁时而导致被阻塞,这是因为事务 B 向事务 A 生成的间隙锁(范围 (20, 30))中插入了一条记录,而插入意向锁和间隙锁是冲突的,所以事务  B 在获取插入意向锁时就陷入了等待状态。

最后回答,为什么会发生死锁?

本次案例中,事务 A 和事务 B 在执行完后 update 语句后都持有范围为(20, 30)的间隙锁,而接下来的插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁。

总结

两个事务即使生成的间隙锁的范围是一样的,也不会发生冲突,因为间隙锁目的是为了防止其他事务插入数据,因此间隙锁与间隙锁之间是相互兼容的。

在执行插入语句时,如果插入的记录在其他事务持有间隙锁范围内,插入语句就会被阻塞,因为插入语句在碰到间隙锁时,会生成一个插入意向锁,然后插入意向锁和间隙锁之间是互斥的关系。

如果两个事务分别向对方持有的间隙锁范围内插入一条记录,而插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁。

字节二面,差点倒在了MySQL上面相关推荐

  1. (二)QT5.14.2连接MySQL并使用QtableView显示数据表内容

    系列文章目录 第一章:(一)QT5.14.2+MSVC2017(32位/64位)+MySQL连接 第二章:(二)QT5.14.2连接MySQL并使用QtableView显示数据表内容 文章目录 系列文 ...

  2. 阿里二面差点败在这道题:MySQL自增主键为何不是连续的呢?

    面试官:"MySQL主键你一般怎样用?" 心想:"嘿嘿,面试官肯定想问InnoDB引擎索引特性相关知识!" 小龙:"平时主键我一般用自增主键!因为自增 ...

  3. 字节跳动内部学习资料泄露!mysql的安装与配置

    美团面试经历(还原最真实的4面) 美团一面: 1.自我介绍 2.聊项目相关 介绍项目 怎么保证redis与Mysql的数据一致性 见你写了个加随机数预防缓存雪崩,解释一下 未改进和改进后的Jmter测 ...

  4. 太卷了,5年Java程序员竟答不出应届生字节二面?

    11.22一面过 11.23二面挂 上一个部门寄了,给我换了个流程唉 一面 面试官问我,三面面评这么好为啥不继续了,我纳闷了,不是你们挂的我吗 介绍项目,点赞是怎么考量的,热度点赞问题怎么解决,具体怎 ...

  5. 字节跳动面试,倒在了终面上

    字节跳动面经 版权©: https://blog.csdn.net/zhangjingao/article/details/97176702 字节跳动一面 这个面试官不是搞java的,后来告诉我字节跳 ...

  6. 惊险,备战3个月,五面蚂蚁金服差点倒在最后一面

    写在前面 作为程序员,免不了要经历面试这关,虽然平时工作勤勤恳恳,但是面试上面未必能展示的出来,比如平时都是做增删改查的业务系统,面试官非要问你如何处理高并发大数据,本来是写java代码,非要问你大型 ...

  7. cgb二三阶面试题集(mysql mybatis MVC... MVVM)

    1.数据库里索引的作用 索引是数据库中提高查询效率的技术,类似于字典的目录 主要作用为了增加数据的查询速度,它的执行也是有一定条件的,不是加了索引就能够加快查询的效率,由于索引的创建实会占据内存空间的 ...

  8. python动态生成数据库表 orm_Python自动化 【第十二篇】:Python进阶-MySQL和ORM

    本节内容 数据库介绍 mysql 数据库安装使用 mysql管理 mysql 数据类型 常用mysql命令 创建数据库 外键 增删改查表 权限 事务 索引 python 操作mysql ORM sql ...

  9. apache2 php mysql_二、Linux服务器apache2+PHP7+mysql环境配置

    关于网站搭建的基础知识,请见我的另一篇文章 https://www.jianshu.com/p/9c65fcb14e4f 此处不加赘述.本文仅介绍Linux服务器环境的配置. 一.安装包下载与安装 1 ...

最新文章

  1. AI检测制造“零缺陷”
  2. 2015.1.3 让CombBox自动弹出下拉框
  3. 汇编 整数常量 实数常量 字符常量 字符串常量 保留字 标识符 伪指令 指令 nop指令
  4. 【剑指Offer】16重建二叉树
  5. 清除linux缓存命令
  6. android软件开发基础课程(一)
  7. P4233-射命丸文的笔记【NTT,多项式求逆】
  8. 构建openjdk镜像_在Windows上构建OpenJDK
  9. 通过物理映射往虚拟机中传输数据
  10. 阿里云提示微擎被挂图片木马详解
  11. 小程序定制开发和模板软件的区别和优势
  12. 后天淘宝客cms系统源码
  13. 利用R语言实现反正弦转换(arcsine-transformed)
  14. 树莓派用root登陆_树莓派开启root用户权限
  15. 两行Python代码调整视频的亮度
  16. vue生命周期和vue-resource
  17. CSDN蓝桥杯算法题——题解Java版本——切面条
  18. k8s/Kubernetes集群安装
  19. 无线调试——免除数据线的干扰
  20. HDMI是什么设备与计算机连接的接口类型,​hdmi接口有什么用?怎么连接电脑?vga和hdmi的区别...

热门文章

  1. 读《人人都能用英语》
  2. faiss-9: index进阶操作
  3. tensorflow中sess.run()
  4. 苹果 AirPods 耳机总是连到别人的iPhone,该怎么办
  5. 区块链技术过程通俗讲解
  6. 2022年 绩效变革管理规划的5个步骤
  7. Xshell 命令换行
  8. CNN逆卷积层(ConvTranspose2d)输出有马赛克网格
  9. 中国互联网站发展状况及其安全报告(2015)发布
  10. redis原理之快照(rdb)原理