mysql 间隙锁原理深度详解

news2024/11/24 17:29:55

目录

一、前言

二、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.coloradmin.cn/o/949839.html

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

相关文章

2017. 网格游戏;2397. 被列覆盖的最多行数;2202. K 次操作后最大化顶端元素

2017. 网格游戏 核心思想&#xff1a;前缀和枚举。读完题后可以发现&#xff0c;第一个机器人走的路线就像一条分割线&#xff0c;第二个机器人只能获得上面白色部分或者下面白色部分的最大值。这个最大值怎么求&#xff0c;我们可以通过前缀和来求&#xff0c;然后通过枚举转…

【探索C++】输入输出

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

【模拟】算法实战

文章目录 一、算法原理二、算法实战1. leetcode1576 替换所有的问号2. leetcode495 提莫攻击3. leetcode6 N字形变换4. leetcode38 外观数列5. leetcode1419 数青蛙 三、总结 一、算法原理 模拟就是用计算机来模拟题目中要求的操作&#xff0c;模拟题目通常具有代码量大、操作…

网络编程套接字(4):日志和守护进程

文章目录 网络编程套接字(4): 日志和守护进程5. 增加日志功能6. 守护进程6.1 进程知识补充(1) 进程组(2) 任务(3) 会话 6.2 守护进程(1) 概念(2) 创建守护进程 网络编程套接字(4): 日志和守护进程 5. 增加日志功能 接着上篇的内容&#xff0c;增加日志功能可以更好地&#xf…

16.WebSocket聊天室

基于SpringBoot 2.6.11 1.WebSocket WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议&#xff0c;可以在html页面直接使用。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在 WebSocket A…

2021年12月 C/C++(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;书架 John最近买了一个书架用来存放奶牛养殖书籍&#xff0c;但书架很快被存满了&#xff0c;只剩最顶层有空余。 John共有N头奶牛(1 ≤ N ≤ 20,000)&#xff0c;每头奶牛有自己的高度Hi(1 ≤ Hi ≤ 10,000)&#xff0c;N头奶牛的总高度为S。书架高度为B(1 ≤…

亚马逊云科技 云技能孵化营——我的云技能之旅

文章目录 每日一句正能量前言活动流程后记 每日一句正能量 不能在已经获得足够多的成功时&#xff0c;还对自己的能力保持怀疑&#xff0c;露出自信的微笑&#xff0c;走出自信的步伐&#xff0c;做一个自信的人&#xff01; 前言 亚马逊云科技 (Amazon Web Services) 是全球云…

Kao框架学习

中间件&#xff1a;洋葱模型 这是官网上给出的示例&#xff0c;从logger依次往下执行&#xff0c;执行到最底层的response往回退&#xff0c;结构很像同心圆的洋葱从外层向内层再由内层向外层。 next表示暂停当前层的代码进入下一层&#xff0c; 当最后一层执行完毕开始回溯&a…

学习完毕JavaSE的感想

今天&#xff0c;把Java复习完毕了&#xff0c;之前学习的时候&#xff0c;学校里学的总是有限的 &#xff0c;自己上手操作之后才发觉差的很多&#xff0c;部署服务器发现要学操作系统&#xff0c;学完了web基础 &#xff0c;又发现还得学前后端分离vue react这些&#xff0c;…

基于ensp的中大型企业网络安全解决方案的设计与实施

一、需求背景 公司部门具体背景&#xff1a;公司共设有人事部、财务部、销售部、市场部四个部门以及一个员工宿舍楼&#xff0c;公司有对外互联网业务需要提供。公司内存在重要部门需要保护数据安全以及访问控制。 &#xff08;1&#xff09;根据客户需求、部门、拓扑&#xf…

代码搜索技巧

在IDE中搜索代码时&#xff0c;经常会被相近的无关代码干扰&#xff0c;如筛选所有使用协程的代码段&#xff0c; 可见有大量“噪音”。 可使用IDE提供的正则表达式功能 如 使用 \bgo ,即匹配go开头的&#xff0c;且之后为空格的所有选项 使用 \bgo func,即匹配到了所有使用协程…

C++学习vector

1,把list的相关函数都实现出来&#xff08;未完&#xff09; 2&#xff0c; 运行结果&#xff1a;

等保测评各个级别的详细内容

等保测评是指信息系统安全等级保护测评&#xff0c;是我国信息安全领域中的一项重要工作。根据国家标准《信息系统安全等级保护基本要求》(GB/T 22239-2008)和《信息系统安全等级保护测评技术要求》(GB/T 25070-2010)。 等保测评分为五个级别&#xff0c;分别是&#xff1a;一级…

达梦数据库管理用户和创建用户介绍

概述 本文主要对达梦数据库管理用户和创建用户进行介绍和总结。 1.管理用户介绍 1.1 达梦安全机制 任何数据库设计和使用都需要考虑安全机制&#xff0c;达梦数据库采用“三权分立”或“四权分立”的安全机制&#xff0c;将系统中所有的权限按照类型进行划分&#xff0c;为每…

JZ12 矩阵中的路径

剑指Offer编程链接&#xff1a;JZ12 题目描述&#xff1a; 思路&#xff1a;递归回溯的方法&#xff0c;总结一下什么情况需要使用递归&#xff1a; 递归在解决问题时&#xff0c;通常涉及以下情况&#xff1a; 问题可被分解为较小的相似子问题。子问题与原问题具有相同的结…

eclipse设置字体大小

打开IDE&#xff0c;选择window->perferences 选择颜色与字体&#xff0c;选择basic 选中text-font之后选择编辑 选择合适的大小之后选择应用并关闭即可 结果

权限提升-Windows本地提权-AT+SC+PS命令-进程迁移-令牌窃取-getsystem+UAC

权限提升基础信息 1、具体有哪些权限需要我们了解掌握的&#xff1f; 后台权限&#xff0c;网站权限&#xff0c;数据库权限&#xff0c;接口权限&#xff0c;系统权限&#xff0c;域控权限等 2、以上常见权限获取方法简要归类说明&#xff1f; 后台权限&#xff1a;SQL注入,数…

ELK日志收集系统

一、概述 1、ELK由三个组件构成 2、作用 日志收集 日志分析 日志可视化 3、为什么使用&#xff1f; 日志对于分析系统、应用的状态十分重要&#xff0c;但一般日志的量会比较大&#xff0c;并且比较分散。 如果管理的服务器或者程序比较少的情况我们还可以逐一…

数据结构学习 --4 串

数据结构学习 --1 绪论 数据结构学习 --2 线性表 数据结构学习 --3 栈&#xff0c;队列和数组 数据结构学习 --4 串 数据结构学习 --5 树和二叉树 数据结构学习 --6 图 数据结构学习 --7 查找 数据结构学习 --8 排序 本人学习记录使用 希望对大家帮助 不当之处希望大家帮忙纠正…