mysql 间隙锁原理深度详解

目录

一、前言

二、mysql之mvcc

2.1 什么是mvcc

2.2 mvcc组成

2.2.1 Undo log 多版本链

2.2.2 ReadView

2.2.3 快照读与当前读

三、RR级别下的事务问题

3.1 RR隔离级别解决的问题

3.1.1 幻读问题

3.2 幻读效果演示

3.2.1 准备测试表和数据

3.2.2 修改事务级别

3.2.3 开启两个session会话并执行事务操作

3.3 间隙锁解决幻读问题

3.3.1 间隙锁概述

3.3.2 基于快照读解决幻读问题

3.3.3 当前读基于间隙锁解决幻读问题

3.4 可重复读一定解决了幻读问题吗

3.4.1 原因分析

3.4.2 总结

四、写在文末


一、前言

锁是mysql提供的一种保证不同事务读写隔离的重要措施,通过锁机制可以有效提升决多线程下并发处理事务能力。mysql根据使用场景不同,对锁的分类有很多种,比如按照锁的粒度可以分为表锁与行锁,按照锁状态可分为共享锁与排他锁,按模式可分为乐观锁与悲观锁等。不同的锁划分对应着不同的使用场景,同时锁的使用也与mysql的事务隔离机制息息相关,本文来深入探讨一下mysql的另一种容易被忽视的锁,即间隙锁,以及与之相关的相关问题。

二、mysql之mvcc

在正式开始聊间隙锁之前,还需要了解下mysql的mvcc机制,因为间隙锁的由来与mysql的事务关系密切,同时事务的底层控制是由mysql的mvcc机制来保障。循着这个思路,我们逐渐拨开迷雾,步步为营向前进。

2.1 什么是mvcc

mvc全称多版本并发控制,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制。

通过这项技术,使得在InnoDB的事务隔离级别下执行 一致性读操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的数据行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。

2.2 mvcc组成

mvcc的实现主要依赖下面的3个主要逻辑实现,分别是:

  • 隐藏字段,在上文中有所交待,每个数据行都会存在一个隐藏字段;
  • undolog版本链,上文有所交待,记录了回滚数据行的数据;
  • ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id,可能是一个数组;

MVCC核心就是 Undo log多版本链 + Read view,“MV”就是通过 Undo log来保存数据的历史版本,实现多版本的管理。“CC”是通过 Read-view来实现管理,通过 Read-view原则来决定数据是否显示。同时针对不同的隔离级别, Read view的生成策略不同,也就实现了不同的隔离级别。

2.2.1 Undo log 多版本链

undo log 也成为回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚 ( 保证事务的原子性 ) 和 MVCC(多版本并发控制 ) 。

举例来说,某一次使用update语句修改一条id为1的数据,如果事务提交失败,那么就需要回滚数据,mysql引擎怎么知道回滚到哪里呢?那就要借助undo log了,undolog中记录了修改之前的数据,所以就可以用于事务回滚。

对于每次操作一条数据的事务来说,每条数据都有两个隐藏字段:

  • trx_id: 事务id,记录最近一次更新这条数据的事务id;
  • roll_pointer: 回滚指针,指向之前生成的undo log;

如下图所示,是关于mysql事务操作时对应的undo log版本链的示意图,记录了多个事务对同一条数据发生修改时undo log的情况;

从上图不难看出,每条数据都可能存在多个版本,不同版本之间,通过undo log链条进行连接,通过这种设计,可保证每个事务提交时,一旦需要回滚,能保证同一个事务只能读取到比当前版本更早提交的值,而不能看到更晚提交的值。

2.2.2 ReadView

Read View是 InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读已提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。

  • Read View简单理解就是对数据在某个时刻的状态拍成照片记录下来。那么之后获取某时刻的数据时就还是原来的照片上的数据,是不会变的;
  • ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id,可能是一个数组;

Read View中比较重要的字段有4个:

  • m_ids : 用来表示MySQL中哪些事务正在执行,但是没有提交;
  • min_trx_id: 就是m_ids里最小的值;
  • max_trx_id : 下一个要生成的事务id值,也就是最大事务id;
  • creator_trx_id: 就是你这个事务的id;

如下图,记录了Read View中当前事务发生状态时相关的几个字段信息,对照上面的几个字段的解释可以进一步理解,举例来说,某个事务第一次执行查询,生成了一致性视图read-view,里面保存了当前事务相关的信息,再次查询时就会从undo log 中拿最新的一条记录开始跟 read-view 做对比,如果不符合比较规则,就根据回滚指针回滚到上一条记录继续比较,直到得到符合比较条件的查询结果。

Read View如何判断记录的某个版本可见呢?规则大致如下:

1)如果当前记录的事务id落在绿色部分(trx_id < min_id),表示这个版本是已提交的事务生成的,可读;

2)如果当前记录的事务id落在红色部分(trx_id > max_id),表示这个版本是由将来启动的事务生成的,不可读;

3)如果当前记录的事务id落在黄色部分(min_id <= trx_id <= max_id),则又可以分为两种情况:

  • 若当前记录的事务id在未提交事务的数组中,则此条记录不可读;
  • 若当前记录的事务id不在未提交事务的数组中,则此条记录可读;

在mysql的事务隔离级别中,RC(读已提交) 和 RR(可重复读) 隔离级别都是基于 MVCC 实现,区别在于:

  • RC 隔离级别时,read-view 是每次执行 select 语句时都生成一个;
  • RR 隔离级别时,read-view 是在第一次执行 select 语句时生成一个,同一事务中后面的所有 select 语句都复用这个 read-view ;

2.2.3 快照读与当前读

快照读

快照读又叫一致性读,读取的是快照数据。不加锁简单的 SELECT 都属于快照读,即不加锁的非阻塞读,比如这样:SELECT * FROM user WHERE ...

之所以出现快照读,是基于提高并发性能考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。

当前读

读取的是记录最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:

SELECT * FROM student LOCK IN SHARE MODE; # 共享锁

SELECT * FROM student FOR UPDATE; # 排他锁

三、RR级别下的事务问题

RR即可重复读,即一个事务执行过程中看到的数据,总是跟这个事务在第一次执行时看到的数据是一致的。在学习mysql的事务隔离级别以及各隔离级别所能解决的问题时,是否还记得在这种隔离级别下能够解决什么问题?以及仍存在什么问呢?

3.1 RR隔离级别解决的问题

下面这张表,详细列举了各事务隔离级别下能够解决的问题,以及未能解决的问题,对照RR隔离级别来说,默认情况下,RR级别可以解决脏读和不可重复读问题,但是仍未解决幻读问题。

3.1.1 幻读问题

简单来说,幻读是指当用户读取某一范围的数据行时,另一个事务又在该范围插入了新行,当用户在读取该范围的数据时会发现有新的幻影行。

注意,在可重复读隔离级别时,默认情况下,普通的查询是快照读(后面的查询一直用的是初次保存的快照数据),因此是不会看到别的事务插入的数据的。因此, 幻读在“当前读”下才会出现(查询语句添加for update,表示当前读),很多人在这里容易糊涂,也是容易混淆一刀切的地方(经常会有面试官问:RR隔离级别下,一定会出现幻读问题吗?所以需要区分是快照读还是当前读,后面会通过案例演示说明);

MVCC多版本并发控制中,读操作可以分为两类: 快照读(Snapshot Read)与当前读 (Current Read)。上述对快照读和当前读有过介绍,它们解决的问题主要如下:

快照读

快照读可以使普通的SELECT 读取数据时不用对表数据进行加锁,从而解决了因为对数据库表的加锁而导致的两个如下问题:

1)解决因加锁导致的修改数据时无法对数据读取问题;

2)解决因加锁导致读取数据时无法对数据进行修改的问题

当前读

当前读是读取的数据库最新的数据,当前读和快照读不同,因为要读取最新的数据而且要保证事务的隔离性,所以当前读是需要对数据进行加锁的(插入/更新/删除操作,属于当前读,需要加锁 , select for update 为当前读)

3.2 幻读效果演示

下面演示基于读已提交事务隔离级别下的幻读效果演示

3.2.1 准备测试表和数据

创建如下表,并插入几条数据;

CREATE TABLE `test` (
  `id` int(12) NOT NULL,
  `x` int(12) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into test values(1,3);
insert into test values(2,3);
insert into test values(3,3);
insert into test values(5,3);
insert into test values(17,3);

完整操作步骤

顺序事务A事务B
1begin;
2select * from test where x=3 for update;
3insert into test values(19,3);
4select * from test where x=3 for update;
5commit;

3.2.2 修改事务级别

检查当前数据库事务隔离级别,默认情况下,事务隔离级别为可重复读;

SELECT @@tx_isolation;

为了模拟幻读效果,先手动调整一下会话的事务隔离级别,使用下面的命令调整

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

设置完成后,再次查询时,看到事务隔离级别就变成了读已提交;

3.2.3 开启两个session会话并执行事务操作

在第一个mysql的session会话窗口执行如下命令

begin;
select * from test where x=3 for update;

此时在第二个会话窗口insert一条数据

再在第一个会话窗口查询x=3的数据,检查数据,发现能够查询到上面插入的这条数据;

3.3 间隙锁解决幻读问题

3.3.1 间隙锁概述

幻读是如何产生的呢?产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了 next-key 锁,就是记录锁和间隙锁的组合。

  • RecordLock锁:锁定单个行记录的锁。(记录锁,RC、RR隔离级别都支持);
  • GapLock锁:间隙锁,锁定索引记录间隙(不包括记录本身),确保索引记录的间隙不变。(范围锁,RR隔离级别支持);
  • Next-key Lock 锁:记录锁和间隙锁组合,同时锁住数据,并且锁住数据前后范围。(记录锁+范围锁,RR隔离级别支持);

可以对照下面这张图深入理解上面几种锁的含义

3.3.2 基于快照读解决幻读问题

完整的操作步骤和顺序如下表

顺序事务1事务2
1begin;
2select * from test where id>1;begin;
3insert into test values(20,3);
4commit;
5select * from test where id>1;
6commit;

仍然使用上面的表,在开始之前,先将事务隔离级别调整为可重复读;

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT @@tx_isolation;

开启第一个会话,查询id>1的数据

begin;
select * from test where id > 1;

开启第二个会话并插入一条数据

begin;
insert into test values(20,3);
commit;

第一个会话再次查询id>1的数据,可以发现第二个会话插入的数据在当前的会话事务中并没有查到;

提交第一个会话的事务,再次查询,此时就能查到数据了

总结:

可重复读隔离级别下是通过MVCC来避免幻读的,具体的实现方式在事务开启后的第一条select语句生成一张Read View(数据库系统当前的一个快照),之后的每一次快照读都会读取这个Read View。

在上面的操作流程中,在第2步生成一张Read View,所以在第5步时读取到数据和第2步相同,避免了幻读。

3.3.3 当前读基于间隙锁解决幻读问题

select lock in share mode(共享锁), select for update ; update, insert ,delete这些操作都是一种当前读,读取的是记录的最新版本。在当前读情况下是通过next-key lock(间隙锁)来避免幻读,即加锁阻塞其他事务的当前读。

操作步骤如下:

顺序事务A事务B
1begin;
2select * from test where id>1 for update;begin;
3insert into test values(20,3);

第一个会话事务执行如下操作

begin;
select * from test where id>1 for update;

第二个会话事务开启事务,insert一条数据

begin;
insert into test values(20,3);

通过上面的现象可以看到,第二个会话事务将会阻塞而不能插入成功;

事务A在第2步执行了select for update当前读,会对id>1的数据行记录加锁,同时对(2,+∞)这个区间加间隙锁,两个都是排它锁,会阻塞其他事务的当前读,所以在第2个事务insert新数据时阻塞,从而避免了当前读情况下的幻读。

3.4 可重复读一定解决了幻读问题吗

mysql默认的事务隔离级(可重复读)下可解决大多数场景下的幻读问题,但某些场景下仍然无法完全解决,看下面的这个操作;

顺序事务A事务B
1begin;
2select * from test where id>1;begin;
3insert into test values(21,3);
4commit;
5select * from test where id>1 for update;
6commit;

有兴趣的同学可以按照这个步骤操作一下看下效果,针对上面的操作来做一下分析:

  • 事务A在第2步使用的是快照读,此时生成了Read View查询出来的数据是id>1这个区间的所有数据;
  • 事务B在第3步插入了一条id为21的数据,因为事务A没有对数据加锁,所以事务B可以正常插入;
  • 第5步事务A查询时查出了事务B插入的数据,因此产生幻读;

3.4.1 原因分析

第5步的时候使用了for update,即使用的是当前读,不会再读取Read View,而读取的是当前最新的数据,所以读出了事务B插入的数据。

3.4.2 总结

结合上面的分析结果,做最后如下小结

  • MySQL默认隔离级别可重复读很大程度上解决了幻读问题,在快照读情况下是通过MVCC解决,在第一次执行查询时生成一张Read View,后续每次快照读都是读这张Read View;
  • 在当前读情况下是加锁来解决,枷锁会阻塞其他事务的当前读,从而避免幻读;
  • 然而可重复读并不能完全解决幻读,比如当一个事务里面使用快照读之后又使用当前读的话就还是可能会出现幻读。

四、写在文末

事务隔离级别是mysql中非常重要的一个点,同时其底层原理也是许多开发者不太好理解的地方,尤其是当事务与锁结合在一起的时候更是容易让人混乱,不管是面试,还是想深入搞清楚原理,或者是排查生产故障问题,搞清不同事务隔离级别以及所能解决的问题,具有很重要的意义。本篇到此结束,感谢观看。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习实战之用 Scikit-Learn 正则化方法解决过拟合详解

你是不是在模型训练中遇到过这样的问题&#xff1a;在训练集上表现得极好&#xff0c;但在测试集上效果不佳&#xff1f;这就是过拟合的问题。 过拟合是模型在训练过程中学到了数据的“噪声”而非规律&#xff0c;导致在未知数据上表现不佳。那么怎么解决这个问题呢&#xff1…

java八股文面试[多线程]——阻塞队列

阻塞队列大纲&#xff1a; 什么是阻塞队列 阻塞队列&#xff1a;从名字可以看出&#xff0c;他也是队列的一种&#xff0c;那么他肯定是一个先进先出&#xff08;FIFO&#xff09;的数据结构。与普通队列不同的是&#xff0c;他支持两个附加操作&#xff0c;即阻塞添加和阻塞删…

excel 无法删除有合并单元格的列内容时的替代方法

背景&#xff1a; hp 笔记本电脑&#xff1b;win10 64位&#xff1b;excel 版本 16.0&#xff1b; office 2016自带excel 问题&#xff1a; 把pdf转excel后&#xff0c;由于原 pdf 图表本身的原因&#xff0c;转换后有不规则合并单元格的现象。 而在选择某列进行“删除” &a…

Open3D(C++) 点云格网分块

目录 一、算法概述二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法概述 点云格网分块是点云二维格网化的一个具体应用案例,与Open3D (C++) 使用点云创建数字高程模型DEM类似,对每个格…

Linux编程--进程--fork使用,创建父子进程

1.使用fork函数创建一个进程 #include <unistd.h>pid_t fork(void); 返回值为0&#xff0c;代表当前进程是子进程 返回值为非负数&#xff0c;代表当前进程为父进程 调用失败&#xff0c;返回-1 代码&#xff1a; #include <stdio.h> #include <sys/types.h&g…

PHP旅游管理系统Dreamweaver开发mysql数据库web结构php编程计算机网页

一、源码特点 PHP 旅游管理系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 PHP 旅游管理系统 源码下载地址&#xff1a; https://download.csdn.net/download/qq_41…

鸿蒙系列-如何使用好 ArkUI 的 @Reusable?

如何使用好 ArkUI 的 Reusable&#xff1f; OpenHarmony 组件复用机制 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为 系统组件&#xff0c;由开发者定义的称为 自定义组件。 在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合…

python实现语音识别

1. 首先安装依赖库 pip install playsound # 该库用于播放音频文件 pip install speech_recognition # 该库用于语音识别 pip install PocketSphinx # 语音识别模块中只有sphinx支持离线的&#xff0c;使用该模块需单独安装 pip install pyttsx3 # 该库用于将文本转换为语音播…

自动化运维工具-----Ansible入门详解

目录 一.Ansible简介 什么是Ansible&#xff1f; Ansible的特点 Ansible的架构 二.Ansible任务执行解析 ansible任务执行模式 ansible执行流程 ansible命令执行过程 三.Ansible配置解析 ansible的安装方式 ansible的程序结构&#xff08;yum安装为例&#xff09; ansibl…

设计模式-5--适配器模式(Adapter Pattern)

一、什么是适配器模式&#xff08;Adapter Pattern&#xff09; 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式主要用于解决不兼容接口之间的问题&#xff0c;使得原本…

Netty-ChannelPipeline

EventLoop可以说是 Netty 的调度中心&#xff0c;负责监听多种事件类型&#xff1a;I/O 事件、信号事件、定时事件等&#xff0c;然而实际的业务处理逻辑则是由 ChannelPipeline 中所定义的 ChannelHandler 完成的&#xff0c;ChannelPipeline 和 ChannelHandler应用开发的过程…

[dasctf]misc05

盲水印 png里藏jpg&#xff0c;bwm.py可以提取含flag的图片

git学习笔记 | 版本管理 - 分支管理

文章目录 git学习笔记Git是什么仓库常见的命令commit 备注规范在文件下设置git忽略文件 .gitignore 版本管理git log | git reflog 查看提交日志/历史版本版本穿梭 git resetgit reset HEAD <file> git checkout -- fileName 丢弃工作区的修改git revertGit恢复之前版本的…

论文阅读_扩散模型_SDXL

英文名称: SDXL: Improving Latent Diffusion Models for High-Resolution Image Synthesis 中文名称: SDXL&#xff1a;改进潜在扩散模型的高分辨率图像合成 论文地址: http://arxiv.org/abs/2307.01952 代码: https://github.com/Stability-AI/generative-models 时间: 2023-…

3D视觉测量:形位公差 面对面平行度(附源码)

文章目录 0. 测试效果1. 基本内容1.1 面对面平行度概述2. 代码实现文章目录:3D视觉测量目录微信:dhlddxB站: Non-Stop_0. 测试效果 1. 基本内容 “平行度” 是用来描述两个或多个对象或方向之间的平行关系的度量。在几何和工程学中,平行度通常用于衡量物体、表面、线条或方向…

mac帧 arp

1.分片 2.MSS max segment size 3.跨网络的本质 就是经历很多的子网或者局域网 4.将数据从A主机跨网络送到B主机的能力 IP和mac IP解决的是路径选择的问题 5.数据链路层 用于两个设备&#xff08;同一种数据链路节点&#xff09;之间进行传递 6.以太网ether 7.局域网通…

圆圈加数字的css

方式一 .circle { width: 50px; height: 50px; border-radius: 50%; background-color: #f00; color: #fff; text-align: center; line-height: 50px; } .circle::before { content: attr(data-number); display: block; } <div class"circle" data-number"…

ChatGPT总结(持续更新)

目录 体验渠道 weTab CSDN-AI助手 其他插件 ChatGPT简介 ChatGPT主要用途 ChatGPT发展历程 GPT-4架构的特点和优势 ChatGPT的工作原理 神经网络和自然语言处理技术 Transformer模型 模型训练优化技巧 ChatGPT对程序员的帮助 与ChatGPT交互和提问技巧 ChatGPT未来…

50ETF期权开户平台(0门槛期权开户指南)

50ETF期权开户平台比较好的有&#xff1a;期权馆&#xff0c;期权科普馆&#xff0c;小熊期权&#xff0c;期权酱&#xff0c;财顺财经&#xff0c;财顺期权等&#xff0c;都是国内前十的期权分仓平台&#xff0c;下文为大家结算50ETF期权开户平台&#xff08;0门槛期权开户指南…

SoC 总线结构学习记录之系统存储总线(System Memory Bus)与私有设备总线

蜂鸟 E203 SOC总线结构&#xff1a;  蜂鸟 E203 内核 BIU 的系统存储接口 ICB 连接系统存储总线&#xff0c;通过其访问 SoC 中的若干存储组件&#xff0c;譬如 ROM&#xff0c;Flash 的只读区间等。  蜂鸟 E203 内核 BIU 的私有设备接口 ICB 连接私有设备总线&#xff0c…