事务04之死锁,锁底层和隔离机制原理

死锁和事务底层原理

文章目录

  • 死锁和事务底层原理
    • 一:MySQL中的死锁现象
      • 1:何为死锁
        • 1.1:死锁的概念
        • 1.2:死锁产生的四个必要条件:
      • 2:MySQL的死锁
        • 2.1:死锁的触发
        • 2.2:MySQL的死锁如何解决的
    • 二:锁机制的底层实现原理
      • 1:锁的内存结构
      • 2:InnoDB的锁优化实现
      • 3:MySQL获取锁的过程
    • 三:事务隔离机制的底层实现
      • 1:读未提交RU的底层实现
      • 2:读已提交RC的底层实现
      • 3:可重复读RR的底层实现
      • 4:序列化的底层实现
      • 5:总结

一:MySQL中的死锁现象

对于多线程与锁而言,存在一个100%会出现的偶发问题,即死锁问题

1:何为死锁

1.1:死锁的概念

所谓死锁:就是多个进程之间由于资源的竞争而造成的阻塞现象,如果没有外力作用,这些进程将无法继续推进

和死锁相似的概念是饥饿:等待时间过长以至于给进程推进和响应造成了明显的影响,“饿而不死”

死锁产生的原因:系统的资源竞争、进程推进顺序非法

1.2:死锁产生的四个必要条件:
  • 互斥条件:共享资源的排他性访问(互斥是基本前提)
  • 不剥夺条件:访问时该共享资源不会被剥夺,而是需要共享资源的进程主动释放(不能蛮横的抢)
  • 请求并保持:保持当前的资源而请求另外的资源(不放弃自己的)
  • 循环等待条件:存在共享资源的循环等待链(A -> B -> C -> A)

在这里插入图片描述

2:MySQL的死锁

2.1:死锁的触发

MySQLRedis、Nginx这类单线程工作的程序不同,它属于一种内部采用多线程工作的应用,因而不可避免的就会产生死锁问题

举一个简单的转账的例子:

在这里插入图片描述
T1等待T2释放锁、T2等待T1释放锁,双方各自等待对方释放锁,一直如此僵持下去,最终就引发了死锁问题

MySQL面对死锁怎么办呢?模拟一下呗:

1:先创建一个表,注意这个表的主键一定要设置成为一会儿update中的where字段

CREATE TABLE `springboot`.`cuihaida_account`  (
`user_name` varchar(255) NOT NULL COMMENT '姓名',
`account` int NOT NULL COMMENT '账户',
PRIMARY KEY (`user_name`) # 因为MySQL记录锁的特性,只有条件命中索引的时候才加的是行锁,否则是表锁
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci;# 插入两条数据
insert into `cuihaida_account` values ('张三', 100);
insert into `cuihaida_account` values ('李四', 100);

2:开启两个终端,准备进行测试
在这里插入图片描述
因此,当死锁问题出现时,MySQL会自动检测并介入,强制回滚结束一个“死锁的参与者(事务)”,从而打破死锁的僵局,让另一个事务能继续执行

2.2:MySQL的死锁如何解决的

对于解决死锁问题可以从多个维度出发,比如预防死锁、避免死锁、解除死锁等,当死锁问题出现后一般只有两种方案:

  • 锁超时机制:事务/线程在等待锁时,超出一定时间后自动放弃等待并返回。
  • 外力介入打破僵局:第三者介入,将死锁情况中的某个事务/线程强制结束,让其他事务继续执行。

锁超时机制

InnoDB中其实提供了锁的超时机制,也就是一个事务在长时间内无法获取到锁时,就会主动放弃等待,抛出相关的错误码及信息,然后返回给客户端

时间限制到底是多久呢?可以通过如下命令查询:

SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

在这里插入图片描述
50s内未获取到锁的事务,会自动结束并返回。

那也就意味着当死锁情况出现时,这个死锁过程最多持续50s,然后其中就会有一个事务主动退出竞争,释放持有的锁资源

但实际业务中,仅依赖超时机制去解除死锁是不够的,毕竟高并发情况下,50s时间太长了,会导致越来越多的事务阻塞。

这个参数调小会好吗?

参数调小之后确实能确保死锁发生后,在很短的时间内可以自动解除

但改掉了这个参数之后,也会影响正常事务等待锁的时间,也就是大部分未发生死锁,但需要等待锁资源的事务,在等待短时间之后,就会立马报错并返回

这显然并不合理,毕竟容易误伤“友军”。

死锁检测算法(wait-for graph)

这种算法是专门用于检测死锁问题的,在该算法中会对于目前库中所有活跃的事务生成等待图,例如:【可以类比os死锁中的死锁定理中的资源分配图】

在这里插入图片描述
MySQL发现了这种等待闭环时,就会强制介入,回滚结束其中一个事务,强制打破该闭环,从而解除死锁问题。

wait-for graph是怎么工作的?[了解下即可]

wait-for graph算法被启用后,会要求MySQL收集两个信息:

  • 锁的信息链表:目前持有每个锁的事务是谁。
  • 事务等待链表:阻塞的事务要等待的锁是谁。

每当一个事务需要阻塞等待某个锁时,就会触发一次wait-for graph算法

该算法会以当前事务作为起点,然后从「锁的信息链表」中找到对应中锁信息。再去根据锁的持有者(事务),在「事务等待链表」中进行查找,看看持有锁的事务是否在等待获取其他锁,如果是,则再去看看另一个持有锁的事务,是否在等待其他锁…,经过一系列的判断后,再看看是否会出现闭环,出现的话则介入破坏。就形成了类似于下面的这张图:

在这里插入图片描述
此时当T3事务需要阻塞等待获取X1锁时,就会触发一次wait-for graph算法,流程如下:

  1. 先根据T3要获取的X1锁,在「锁的信息链表」中找到X1锁的持有者T1
  2. 再在「事务等待链表」中查找,看看T1是否在等待获取其他锁,此时会得知T1等待X2
  3. 再去「锁的信息链表」中找到X2锁的持有者T2,再看看T2是否在阻塞等待获取其他锁。
  4. 再在「事务等待链表」中查找T2,发现T2正在等待获取X3锁,再找X3锁的持有者。

经过上述一系列算法过程后,最终会发现X3锁的持有者为T3,而本次算法又正是T3事务触发的,此时又回到了T3事务,也就代表着产生了“闭环”,因此也可以证明这里出现了死锁现象,所以MySQL会强制回滚其中的一个事务,来抵达解除死锁的目的。

出现死锁问题时,MySQL会选择哪个事务回滚呢?

当一个事务在执行SQL更改数据时,都会记录在Undo-log日志中

Undo量越小的事务,代表它对数据的更改越少,同时回滚的代价最低,因此会选择Undo量最小的事务回滚

如若两个事务的Undo量相同,会选择回滚触发死锁的事务

🎉 可以通过innodb_deadlock_detect=on|off这个参数,来控制是否开启死锁检测机制。

在这里插入图片描述
⚠️死锁检测机制在MySQL后续的高版本中是默认开启的,但实际上死锁检测的开销不小,当阻塞的并发事务越来越多时,检测的效率也会呈线性增长

如何避免死锁产生

因为死锁的检测过程较为耗时,所以尽量不要等死锁出现后再去解除,而是尽量调整业务避免死锁的产生,一般来说可以从如下方面考虑:

  • 合理的设计索引结构,使业务SQL在执行时能通过索引定位到具体的几行数据,减小锁的粒度。
  • 业务允许的情况下,也可以将隔离级别调低,因为级别越低,锁的限制会越小。
  • 调整业务SQL的逻辑顺序,较大、耗时较长的事务尽量放在特定时间去执行(如凌晨对账…)。
  • 尽可能的拆分业务的粒度,一个业务组成的大事务,尽量拆成多个小事务,缩短一个事务持有锁的时间。
  • 如果没有强制性要求,就尽量不要手动在事务中获取排他锁,否则会造成一些不必要的锁出现,增大产生死锁的几率。

🎉 简单来说,在业务允许的情况下,尽量缩短一个事务持有锁的时间、减小锁的粒度以及锁的数量。

⚠️ 当MySQL运行过程中产生了死锁问题,那这个死锁问题以后绝对会再次出现,当死锁被MySQL自己解除后,一定要记住去排除业务SQL的执行逻辑,找到产生死锁的业务,然后调整业务SQL的执行顺序,这样才能从根源上避免死锁产生

二:锁机制的底层实现原理

1:锁的内存结构

Java中,Synchronized锁是基于Monitor实现的,而ReetrantLock又是基于AQS实现的,那MySQL的锁是基于啥实现的呢?

在这里插入图片描述

锁的事务信息

其中记录着当前的锁结构是由哪个事务生成的,记录的是指针,指向一个具体的事务。

索引信息

这个是行锁的特有信息,对于行锁来说,需要记录一下加锁的行数据属于哪个索引、哪个节点,记录的也是指针

锁粒度信息

对于不同粒度的锁,其中存储的信息也并不同:

  • 如果是表锁,其中就记录了一下是对哪张表加的锁,以及表的一些其他信息。

  • 如果锁粒度是行锁,其中记录的信息更多,有三个较为重要的:

    • Space ID:加锁的行数据,所在的表空间ID。

    • Page Number:加锁的行数据,所在的页号。

    • n_bits:使用的比特位,对于一页数据中,加了多少个锁(后续结合讲)。

锁类型信息

对于锁结构的类型,在内部实现了复用,采用一个32bittype_mode来表示

这个32bit的值可以拆为lock_mode、lock_type、rec_lock_type三部分

在这里插入图片描述

lock_mode:表示锁的模式,使用低四位。

  • 0000/0:表示当前锁结构是共享意向锁,即IS锁。
  • 0001/1:表示当前锁结构是排他意向锁,即IX锁。
  • 0010/2:表示当前锁结构是共享锁,即S锁。
  • 0011/3:表示当前锁结构是排他锁,即X锁。
  • 0100/4:表示当前锁结构是自增锁,即AUTO-INC锁。

lock_type:表示锁的类型,使用低位中的5~8位。

  • LOCK_TABLE:当第5个比特位是1时,表示目前是表级锁。
  • LOCK_REC:当第6个比特位是1时,表示目前是行级锁。

rec_lock_type:表示行锁的具体类型,使用其余位。

  • LOCK_ORDINARY:当高23位全零时,表示目前是临键锁。
  • LOCK_GAP:当第10位是1时,表示目前是间隙锁。
  • LOCK_REC_NOT_GAP:当第11位是1时,表示目前是记录锁。
  • LOCK_INSERT_INTENTION:当第12位是1时,表示目前是插入意向锁。
  • .....:内部还有一些其他的锁类型,会使用其他位。

is_waiting:表示目前锁处于等待状态还是持有状态,使用低位中的第9位。

  • 0:表示is_waiting=false,即当前锁无需阻塞等待,是持有状态。
  • 1:表示is_waiting=true,即当前锁需要阻塞,是等待状态。

在这里插入图片描述
例如上面这个图,表示一个阻塞等待的行级排他临键锁结构。

其他信息

这个所谓的其他信息,也就是指一些用于辅助锁机制的信息,例如wait-for graph中的两个辅助链表

锁的比特位

与其说是锁的比特位,不如说是数据的比特位,好比举个例子:

student_idnamesexheight
1xx111
2xxx111
3xxxx112
4xxxxx112
5xxxxxx122
6xxxxxxx112
7xxxxxxxx122

学生表中有七条数据,此时就会形成一个比特数组:000000000

明明只有七条数据,为啥会有9个比特位呢?

因为行锁中,间隙锁可以锁定无穷小、无穷大这两个间隙,因此这组比特中,首位和末位即表示无穷小、无穷大两个间隙。

好比此时T1事务,对ID=2、3、6这三条数据加锁了,此时这个比特数组就会变为001100100,表示T1事务同时锁定了三条数据。

而之前聊到的n_bits,它就会记录一下在这组比特中,多少条记录被上锁了。

2:InnoDB的锁优化实现

如果一个事务同时需要对表中的1000条数据加锁,会生成1000个锁结构吗?

MySQL是基于事务实现的锁,当下面述四点条件被满足时,符合条件的行记录会被放入到同一个锁结构中

  • 目前对表中不同行记录加锁的事务是同一个。(同事务)
  • 需要加锁的记录在同一个页面中。(同页)
  • 目前事务加锁的类型都是相同的。(同类型)
  • 锁的等待状态也是相同的。(同等待状态)

假设加锁的1000条数据分布在3个页面中,同时表中没有其他事务在操作,加的都是同一类型的锁。

此时依据上述的前提条件,那在内存中仅会生成三个锁结构,能够很大程度上减少锁结构的数量

3:MySQL获取锁的过程

当一个事务需要获取某个行锁时,首先会看一下内存中是否存在这条数据的锁结构

如果存在则生成一个锁结构,将其is_waiting对应的比特位改为1,表示目前事务在阻塞等待获取该锁

当其他事务释放锁后,会唤醒当前阻塞的事务,然后会将其is_waiting改为0,接着执行SQL

事务获取锁时,是如何在内存中,判断是否已经存在相同记录的锁结构呢?

还记得锁结构中会记录的一个信息嘛?也就是「锁粒度信息」,如果是表锁,会记录表信息,如果是行锁,会记录表空间、页号等信息。在事务获取锁时,就是去看内存中,已存在的锁结构的这个信息,来判断是否存在其他事务获取了锁。

释放锁的过程也比较简单,这个工作一般是由MySQL自己完成的,当事务结束后会自动释放,释放的时候会去看一下,内存中是否有锁结构,正在等待获取目前释放的锁,如果有则唤醒对应的线程/事务。

三:事务隔离机制的底层实现

  • RU/读未提交级别:要求该隔离级别下解决脏写问题。
  • RC/读已提交级别:要求该隔离级别下解决脏读问题。
  • RR/可重复读级别:要求该隔离级别下解决不可重复读问题。【其实这里已经解决了幻读问题,就是临键锁 + MVCC】
  • Serializable/序列化级别:要求在该隔离级别下解决幻读问题。

1:读未提交RU的底层实现

RU级别要求一个事务可以读到其他事务未提交的数据,但同时要求解决脏写(更新覆盖)问题

假设没有加锁

在这里插入图片描述
因此,对于解决脏写问题,RU采取的是读取的时候不加锁,写入的时候加入排他锁,防止其他事务的写覆盖掉自己的写

在这里插入图片描述

2:读已提交RC的底层实现

RC级别要求解决脏读问题,也就是一个事务中,不允许读另一个事务还未提交的数据

RC级别的底层实现,对于写操作会加排他锁,而读操作会使用MVCC机制。

但由于每次select时都会生成ReadView快照,此时就会出现下述问题:

在这里插入图片描述
此时观察这个案例,明明是在一个事务中查询同一条数据,结果两次查询的结果并不一致,这也是所谓的不可重复读的问题。

3:可重复读RR的底层实现

RC级别中,虽然解决了脏读问题,但依旧存在不可重复读问题,而RR级别中,就是要确保一个事务中的多次读取结果一致,即解决不可重复读问题

两种方案:

  • 查询时,对目标数据加上临键锁,即读操作执行时,不允许其他事务改动数据
  • MVCC机制的优化版:一个事务中只生成一次ReadView快照

在这里插入图片描述

4:序列化的底层实现

在这个级别中,要求解决所有可能会因并发事务引发的问题

序列化就是所有写操作加临键锁(具备互斥特性),所有读操作加共享锁。

由于所有写操作在执行时,都会获取临键锁,所以写-写、读-写、写-读这类并发场景都会互斥

由于读操作加的是共享锁,因此在Serializable级别中,只有读-读场景可以并发执行。

5:总结

  • U级别:读操作不加锁,写操作加排他锁。
  • RC级别:读操作使用MVCC机制,每次SELECT生成快照,写操作加排他锁。
  • RR级别:读操作使用MVCC机制,首次SELECT生成快照,写操作加临键锁。
  • 序列化级别:读操作加共享锁,写操作加临键锁。
级别/场景读-读读-写/写-读写-写
RU级别并行执行并行执行串行执行
RC级别并行执行并行执行串行执行
RR级别并行执行并行执行串行执行
序列化级别并行执行串行执行串行执行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/68675.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

布林线(BOLL)

BOLL上轨的意义 BOLL指标由上轨、中轨和下轨组成,上轨是股价运行的“压力线”,当股价突破上轨时,通常意味着市场处于极度强势的上涨行情。但如果股价在突破上轨后无法持续维持在上轨上方,而是开始回落并跌破上轨,这往往…

Fiddler(一) - Fiddler简介_fiddler软件

文章目录 一、为什么选择Fiddler作为抓包工具? 二、什么是Fiddler?三、Fiddler使用界面简介四、延伸阅读 一、为什么选择Fiddler作为抓包工具? 抓包工具有很多,小到最常用的web调试工具firebug,大到通用性强大的抓包工具wireshark。为什么使用fid…

安卓安全访问配置说明network-security-config —未来之窗跨平台操作

一、放行特定的 IP 地址和端口 <?xml version"1.0" encoding"utf-8"?> <network-security-config><domain-config><domain>您要放行的特定 IP 地址</domain><port>您要放行的端口号</port></domain-confi…

Harbor 部署

harbor镜像仓库搭建 版本v2.10.3 文章目录 一. docker 安装 harbor1. harbor 配置http访问1.1 下载harbor二进制包1.2 修改配置文件1.3 运行1.4 访问 2.【可选】harbor 配置https访问2.1 自签证书2.1 修改配置文件2.3 修改hosts文件2.4 运行2.5 访问 二. k8s 安装harbor1 .安装…

RabbitMQ模块新增消息转换器

文章目录 1.目录结构2.代码1.pom.xml 排除logging2.RabbitMQConfig.java3.RabbitMQAutoConfiguration.java 1.目录结构 2.代码 1.pom.xml 排除logging <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/PO…

win11 sourcetree安装问题

win11 sourcetree安装出现msys-2.0.dll 问题&#xff0c;需要从win10的以下路径复制出 msys-2.0.dll来加入到win11中 C:\Users\kz121468\AppData\Local\Atlassian\SourceTree\git_local\usr\bin\ 复制到 win11的 C:\Users\kz121468\AppData\Local\Atlassian\SourceTree\git_lo…

Qt事件处理:理解处理器、过滤器与事件系统

1. 事件 事件 是一个描述应用程序中、发生的某些事情的对象。 在 Qt 中&#xff0c;所有事件都继承自 QEvent &#xff0c;并且每个事件都有特定的标识符&#xff0c;如&#xff1a;Qt::MouseButtonPress 代表鼠标按下事件。 每个事件对象包含该事件的所有相关信息&#xff…

一文读懂 Faiss:开启高维向量高效检索的大门

一、引言 在大数据与人工智能蓬勃发展的当下&#xff0c;高维向量数据如潮水般涌现。无论是图像、音频、文本&#xff0c;还是生物信息领域&#xff0c;都离不开高维向量来精准刻画数据特征。然而&#xff0c;在海量的高维向量数据中进行快速、准确的相似性搜索&#xff0c;却…

Openfga 授权模型搭建

1.根据项目去启动 配置一个 openfga 服务器 先创建一个 config.yaml文件 cd /opt/openFGA/conf touch ./config.yaml 怎么配置&#xff1f; 根据官网来看 openfga/.config-schema.json at main openfga/openfga GitHub 这里讲述详细的每一个配置每一个类型 这些配置有…

三次方根pow

给定一个浮点数n&#xff0c;求它的三次方根。 输入格式: 共一行&#xff0c;包含一个浮点数n,−10000≤n≤10000。 输出格式: 共一行&#xff0c;包含一个浮点数&#xff0c;表示问题的解。 注意&#xff0c;结果保留6位小数。 输入样例: 1000.00输出样例: 10.000000 …

你好!这是我自己的CSDN博客!

写于2025年1月30日 我是一个普通的嵌入式软件程序员&#xff0c;喜欢研究Linux&#xff08;应用层跟内核从都有粗浅的涉略&#xff09;&#xff0c;单片机&#xff0c;操作系统和计算机体系架构等内容&#xff0c;目前是一枚普通的本科生。 笔者是一个朴素的开源主义者&#xf…

11 Spark面试真题

11 Spark大厂面试真题 1. 通常来说&#xff0c;Spark与MapReduce相比&#xff0c;Spark运行效率更高。请说明效率更高来源于Spark内置的哪些机制&#xff1f;2. hadoop和spark使用场景&#xff1f;3. spark如何保证宕机迅速恢复?4. hadoop和spark的相同点和不同点&#xff1f;…

赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。

佬们过年好呀~新年第一篇博客让我们来场赛博算命吧&#xff01; 更多文章&#xff1a;个人主页 系列文章&#xff1a;JAVA专栏 欢迎各位大佬来访哦~互三必回&#xff01;&#xff01;&#xff01; 文章目录 #一、文化背景概述1.文化起源2.起卦步骤 #二、卦象解读#三、just do i…

列表(列表是什么)

你将学习列表是什么以及如何使用列表元素。列表让你能够在一个地方存储成组的信息&#xff0c;其中可以只包含几个元素&#xff0c;也可以包含数百万个元素。 列表是新手可直接使用的最强大的Python功能之一&#xff0c;它融合了众多重要的编程概念。 列表是什么 列表 由一系列…

Myeclipse最新版本 C1 2019.4.0

Myeclipse C1 2019.4.0下载地址&#xff1a;链接: https://pan.baidu.com/s/1MbOMLewvAdemoQ4FNfL9pQ 提取码: tmf6 1.1、什么是集成开发环境? ★集成开发环境讲究-站式开发&#xff0c;使用这个工具即可。有提示功能&#xff0c;有自动纠错功能。 ★集成开发环境可以让软件开…

CNN的各种知识点(一):卷积神经网络CNN通道数的理解!

卷积神经网络CNN通道数的理解&#xff01; 通道数的核心概念解析1. 通道数的本质 2. 单张灰度图的处理示例&#xff1a; 3. 批量输入的处理通道与批次的关系&#xff1a; 4. RGB三通道输入的处理计算过程&#xff1a;示例&#xff1a; 5. 通道数的实际意义6. 可视化理解(1) 单通…

力扣116. 填充每个节点的下一个右侧节点指针

Problem: 116. 填充每个节点的下一个右侧节点指针 文章目录 题目描述思路复杂度Code 题目描述 思路 遍历思想(利用二叉树的先序遍历) 本题目的难点在于对于不同父节点的邻接问题因此我们可以抽象将两两节点为一组&#xff08;不同父节点的两个孩子节点也抽象为一组&#xff09…

Deepseek r1模型对医疗大模型的发展有什么影响?

1. 强化学习技术的突破与创新 DeepSeek R1 是一款基于纯强化学习&#xff08;RL&#xff09;训练的开源推理模型&#xff0c;其核心在于通过环境反馈而非人工标注数据来优化模型行为。这种方法不仅降低了对标注数据的依赖&#xff0c;还显著提升了模型的推理能力。例如&#x…

python学opencv|读取图像(四十九)原理探究:使用cv2.bitwise()系列函数实现图像按位运算

【0】基础定义 按位与运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;全1取1&#xff0c;其余取0。 按位或运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;有1取1&#xff0c;其余取0。 按位异或运算&#xff1a; 两个等长度二进制数上下对齐&#xff0c;相…

面试经典150题——图的广度优先搜索

文章目录 1、蛇梯棋1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、最小基因变化2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、单词接龙3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 1、蛇梯棋 1.1 题目链接 点击跳转到题目位置 1.2 题目描述 给你一…