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

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

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

相关文章

Acwing798.差分矩阵

前缀和与差分 图文并茂 超详细整理&#xff08;全网最通俗易懂&#xff09;_前缀和差分_林小鹿的博客-CSDN博客 代码展示&#xff1a; #include<iostream> #include<cstdio> using namespace std; const int N 1e3 10; int a[N][N], b[N][N]; void insert(int x…

【UE 材质】实现角度渐变材质、棋盘纹理材质

目标 步骤 一、角度渐变材质 1. 首先通过“Mask”节点将"Texture Coordinate" 节点的R、G通道分离 2. 通过“RemapValueRange”节点将0~1范围映射到-1~1 可以看到此时R通道效果&#xff1a; G通道效果&#xff1a; 继续补充如下节点 二、棋盘纹理材质 原视频链接&…

git分支管理策略

git的基础操作以及常用命令在上篇博客哦~ git原理与基本使用 1.分支管理 1.主分支 在版本回退⾥&#xff0c;我们已经知道&#xff0c;每次提交&#xff0c;Git都把它们串成⼀条时间线&#xff0c;这条时间线就可以理解为是⼀个分⽀。截⽌到⽬前&#xff0c;只有⼀条时间线&…

Docker原理详细剖析-Namespace

一、简介 docker容器技术从2013年开始火了以后&#xff0c;2014年左右当时有幸在学校能和学院教授一起做些项目以及学习。其中docker技术在当时来说还算是比较新的技术&#xff0c;国内关于这块的资料以及使用也才刚刚开始&#xff0c;讨论docker技术&#xff0c;算是相对时髦的…

【办公自动化】使用Python批量处理Excel文件并转为csv文件

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Hbase文档--架构体系

阿丹&#xff1a; 基础概念了解之后了解目标知识的架构体系&#xff0c;就能事半功倍。 架构体系 关键组件介绍&#xff1a; HBase – Hadoop Database&#xff0c;是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;利用HBase技术可在廉价PC Server上搭建起…

WordArt Designer:基于用户驱动与大语言模型的艺术字生成

AIGC推荐 FaceChain人物写真开源项目&#xff0c;支持风格与穿着自定义&#xff0c;登顶github趋势榜首&#xff01; 前言 本文介绍了一个基于用户驱动&#xff0c;依赖于大型语言模型(LLMs)的艺术字生成框架&#xff0c;WordArt Designer。 该系统包含四个关键模块:LLM引擎、…

19.CSS雨云动画特效

效果 源码 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Cloud & Rain Animation</title><link rel="stylesheet" href="style.css"> </head> <bo…

数字证书有什么作用,为什么要用数字证书?

数字证书是一种用于加密和验证数据的安全工具&#xff0c;它在现代通信和互联网领域起着重要的作用。下面安策给大家介绍一下数字证书的几个主要作用和为什么要使用数字证书的原因&#xff1a; 身份认证&#xff1a;数字证书可用于在线交互中验证身份。通过使用证书进行身份认证…

在 Spring Boot 中集成 MinIO 对象存储

MinIO 是一个开源的对象存储服务器&#xff0c;专注于高性能、分布式和兼容S3 API的存储解决方案。本文将介绍如何在 Spring Boot 应用程序中集成 MinIO&#xff0c;以便您可以轻松地将对象存储集成到您的应用中。 安装minio 拉取 minio Docker镜像 docker pull minio/minio创…

GEE/PIE遥感大数据处理与典型案例丨数据整合Reduce、云端数据可视化、数据导入导出及资产管理、机器学习算法等

目录 ​专题一&#xff1a;初识GEE和PIE遥感云平台 专题二&#xff1a;GEE和PIE影像大数据处理基础 专题三&#xff1a;数据整合Reduce 专题四&#xff1a;云端数据可视化 专题五&#xff1a;数据导入导出及资产管理 专题六&#xff1a;机器学习算法 专题七&#xff1a;…

适配器设计模式

目录 一、适配器模式1.类适配器模式2.对象适配器模式3.接口适配器 二、适配器模式应用场景三、适配器模式的优缺点 一、适配器模式 B站&#xff1a;java架构师 定义&#xff1a;适配器模式把一个类的接口变换成客户端所期待的另一种接口&#xff0c;从而使原本因接口不匹配而…

网络字节序——TCP接口及其实现简单TCP服务器

网络字节序——TCP接口及其实现简单TCP服务器 文章目录 网络字节序——TCP接口及其实现简单TCP服务器简单TCP服务器的实现1. 单进程版&#xff1a;客户端串行版2. 多进程版&#xff1a;客户端并行版netstat查看网络信息3.多线程版&#xff1a;并行执行log.hpp 守护进程fg、bg s…

OpenGl图像的位移及旋转

一般而言&#xff0c;改变物体的位置时&#xff0c;需要改变每一帧所有顶点的坐标&#xff0c;计算量巨大 可以将每一个顶点用向量值表示&#xff0c;使用位移矩阵&#xff0c;缩放矩阵&#xff0c;旋转矩阵对顶点进行操作。 顶点着色器&#xff1a; #version 330 core layo…

中欧财富:分布式数据库的应用历程和 TiDB 7.1 新特性探索

作者&#xff1a;张政俊 中欧财富数据库负责人 中欧财富是中欧基金控股的销售子公司&#xff0c;旗下 APP 实现业内基金品种全覆盖&#xff0c;提供基金交易、大数据选基、智慧定投、理财师咨询等投资工具及服务。中欧财富致力为投资者及合作伙伴提供一站式互联网财富管理解决方…

redis缓存雪崩、穿透、击穿解决方案

redis缓存雪崩、穿透、击穿解决方案 背景缓存雪崩缓存击穿缓存穿透总结背景 关于缓存异常,我们常见的有三个问题:缓存雪崩、缓存击穿、缓存穿透。这三个问题一旦发生,会导致大量请求直接落到数据库层面。如果请求的并发量很大,会影响数据库的运行,严重的会导致数据库宕机…

C++ Day6

目录 一、菱形继承 1.1 概念 1.2 格式 二、虚继承 2.1 作用 2.2 格式 2.3注意 三、多态 3.1函数重写 3.2 虚函数 3.3 赋值兼容规则 3.4 多态中&#xff0c;函数重写的原理 3.5 虚析构函数 3.5.1 格式 3.6 纯虚函数 3.6.1格式 四、抽象类 五、模板 5.1模板的特…

C#_GDI+ 绘图编程入门

官网提供相关API GDI 基本图形功能_drawing 高级二维和矢量图形功能_drawing2D GDI 图像处理功能_Imaging GDI 排版功能_text Windows 窗体应用程序提供打印功能_Printing 像素 构成图像的最小单位就是像素&#xff1b;屏幕上显示不管是位图或者矢量图&#xff0c;当描述…

计算机竞赛 基于GRU的 电影评论情感分析 - python 深度学习 情感分类

文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖&#xff0c;适合作为竞…

IP协议分片重组问题

分片是什么&&为什么会有分片 IP数据报分片的主要目的是为了防止IP数据报文长度超过下一跳链路MTU(最大传输单元)。 数据链路层之MTU 数据链路层中有一个东西叫做MTU&#xff08;最大传输单元&#xff09;&#xff0c;它的作用主要是控制上层给的数据报不要太大&#…