第16章_多版本并发控制

第16章_多版本并发控制

🏠个人主页:shark-Gao

🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉

🎉目前状况:23届毕业生,目前在某公司实习👏👏

❤️欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

🖥️个人小站 :个人博客,欢迎大家访问

配套视频参考:MySQL数据库天花板–康师傅

1. 什么是MVCC

MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来实现数据库的 并发控制 。这项技术使得在InnoDB的事务隔离级别下执行 一致性读 操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样 在做查询的时候就不用等待另一个事务释放锁。

MVCC没有正式的标准,在不同的DBMS中MVCC的实现方式可能是不同的,也不是普遍使用的(大家可以参考相关的DBMS文档)。这里讲解InnoDB中MVCC的实现机制(MySQL其他的存储引擎并不支持它)。

2. 快照读与当前读

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突 ,做到 即使有读写冲突时,也能做到 不加锁非阻塞并发读 ,而这个读指的就是 快照读 , 而非 当前读 。当前 读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

2.1 快照读

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

SELECT * FROM player WHERE ...

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

既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

2.2 当前读

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

SELECT * FROM student LOCK IN SHARE MODE; # 共享锁
SELECT * FROM student FOR UPDATE; # 排他锁
INSERT INTO student values ... # 排他锁
DELETE FROM student WHERE ... # 排他锁
UPDATE student SET ... # 排他锁

3. 复习

3.1 再谈隔离级别

我们知道事务有 4 个隔离级别,可能存在三种并发问题:

image-20220714140441064

image-20220714140510426

image-20220714140541555

3.2 隐藏字段、Undo Log版本链

回顾一下undo日志的版本链,对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。

  • trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给 trx_id 隐藏列。
  • roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然 后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
image-20220714140716427

假设插入该记录的事务id8,那么此刻该条记录的示意图如下所示:

image-20220714140801595

insert undo只在事务回滚时起作用,当事务提交后,该类型的undo日志就没用了,它占用的Undo Log Segment也会被系统回收(也就是该undo日志占用的Undo页面链表要么被重用,要么被释放)。

假设之后两个事务id分别为 1020 的事务对这条记录进行 UPDATE 操作,操作流程如下:

image-20220714140846658

image-20220714140908661

每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个 roll_pointer 属性 ( INSERT 操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些 undo日志 都连起来,串成一个链表:

image-20220714141012874

对该记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数 的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 版本链 ,版 本链的头节点就是当前记录最新的值。

每个版本中还包含生成该版本时对应的事务id

4. MVCC实现原理之ReadView

MVCC 的实现依赖于:隐藏字段Undo LogRead View

4.1 什么是ReadView

image-20220714141154235

4.2 设计思路

使用 READ UNCOMMITTED 隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。

使用 SERIALIZABLE 隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。

使用 READ COMMITTEDREPEATABLE READ 隔离级别的事务,都必须保证读到 已经提交了的 事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。

这个ReadView中主要包含4个比较重要的内容,分别如下:

  1. creator_trx_id ,创建这个 Read View 的事务 ID。

    说明:只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为 事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

  2. trx_ids ,表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表

  3. up_limit_id ,活跃的事务中最小的事务 ID。

  4. low_limit_id ,表示生成ReadView时系统中应该分配给下一个事务的 id 值。low_limit_id 是系 统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。

注意:low_limit_id并不是trx_ids中的最大值,事务id是递增分配的。比如,现在有id为1, 2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时, trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是4。

image-20220714142254768

4.3 ReadView的规则

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。

  • 如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的 up_limit_id 值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于或等于ReadView中的 low_limit_id 值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id属性值在ReadView的 up_limit_id 和 low_limit_id 之间,那就需要判断一下trx_id属性值是不是在 trx_ids 列表中。
    • 如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
    • 如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

4.4 MVCC整体操作流程

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

  1. 首先获取事务自己的版本号,也就是事务 ID;
  2. 获取 ReadView;
  3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
  5. 最后返回符合规则的数据。
image-20220715130639408

在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次 Read View。

如表所示:

image-20220715130843147

注意,此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。

当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View,如下表所示:

image-20220715130916437

5. 举例说明

image-20220715131200077

5.1 READ COMMITTED隔离级别下

READ COMMITTED :每次读取数据前都生成一个ReadView。

现在有两个 事务id 分别为 1020 的事务在执行:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...

说明:事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的。所以我们才在事务2中更新一些别的表的记录,目的是让它分配事务id。

此刻,表student 中 id 为 1 的记录得到的版本链表如下所示:

image-20220715133640655

假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行:

# 使用READ COMMITTED隔离级别的事务
BEGIN;

# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
image-20220715134540737

之后,我们把 事务id10 的事务提交一下:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
COMMIT;

然后再到 事务id20 的事务中更新一下表 studentid1 的记录:

# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student中 id1 的记录的版本链就长这样:

image-20220715134839081

然后再到刚才使用 READ COMMITTED 隔离级别的事务中继续查找这个 id 为 1 的记录,如下:

# 使用READ COMMITTED隔离级别的事务
BEGIN;

# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'

# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'王五'
image-20220715135017000 image-20220715135143939

5.2 REPEATABLE READ隔离级别下

使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之后的查询就不会重复生成了。

比如,系统里有两个 事务id 分别为 1020 的事务在执行:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...

此刻,表student 中 id 为 1 的记录得到的版本链表如下所示:

image-20220715140006061

假设现在有一个使用 REPEATABLE READ 隔离级别的事务开始执行:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;

# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
image-20220715140155744

之后,我们把 事务id10 的事务提交一下,就像这样:

# Transaction 10
BEGIN;

UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;

COMMIT;

然后再到 事务id20 的事务中更新一下表 studentid1 的记录:

# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student 中 id1 的记录的版本链长这样:

image-20220715140354217

然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个 id1 的记录,如下:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值仍为'张三'
image-20220715140555172 image-20220715140620328

这次SELECT查询得到的结果是重复的,记录的列c值都是张三,这就是可重复读的含义。如果我们之后再把事务id20的记录提交了,然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个id1的记录,得到的结果还是张三,具体执行过程大家可以自己分析一下。

5.3 如何解决幻读

接下来说明InnoDB 是如何解决幻读的。

假设现在表 student 中只有一条数据,数据内容中,主键 id=1,隐藏的 trx_id=10,它的 undo log 如下图所示。

image-20220715141002035

假设现在有事务 A 和事务 B 并发执行,事务 A 的事务 id 为 20事务 B 的事务 id 为 30

步骤1:事务 A 开始第一次查询数据,查询的 SQL 语句如下。

select * from student where id >= 1;

在开始查询之前,MySQL 会为事务 A 产生一个 ReadView,此时 ReadView 的内容如下: trx_ids= [20,30] , up_limit_id=20 , low_limit_id=31 , creator_trx_id=20

由于此时表 student 中只有一条数据,且符合 where id>=1 条件,因此会查询出来。然后根据 ReadView 机制,发现该行数据的trx_id=10,小于事务 A 的 ReadView 里 up_limit_id,这表示这条数据是事务 A 开启之前,其他事务就已经提交了的数据,因此事务 A 可以读取到。

结论:事务 A 的第一次查询,能读取到一条数据,id=1。

步骤2:接着事务 B(trx_id=30),往表 student 中新插入两条数据,并提交事务。

insert into student(id,name) values(2,'李四');
insert into student(id,name) values(3,'王五');

此时表student 中就有三条数据了,对应的 undo 如下图所示:

image-20220715141208667

步骤3:接着事务 A 开启第二次查询,根据可重复读隔离级别的规则,此时事务 A 并不会再重新生成 ReadView。此时表 student 中的 3 条数据都满足 where id>=1 的条件,因此会先查出来。然后根据 ReadView 机制,判断每条数据是不是都可以被事务 A 看到。

1)首先 id=1 的这条数据,前面已经说过了,可以被事务 A 看到。

2)然后是 id=2 的数据,它的 trx_id=30,此时事务 A 发现,这个值处于 up_limit_id 和 low_limit_id 之 间,因此还需要再判断 30 是否处于 trx_ids 数组内。由于事务 A 的 trx_ids=[20,30],因此在数组内,这表 示 id=2 的这条数据是与事务 A 在同一时刻启动的其他事务提交的,所以这条数据不能让事务 A 看到。

3)同理,id=3 的这条数据,trx_id 也为 30,因此也不能被事务 A 看见。

image-20220715141243993

结论:最终事务 A 的第二次查询,只能查询出 id=1 的这条数据。这和事务 A 的第一次查询的结果是一样 的,因此没有出现幻读现象,所以说在 MySQL 的可重复读隔离级别下,不存在幻读问题。

6. 总结

这里介绍了 MVCC 在 READ COMMITTDREPEATABLE READ 这两种隔离级别的事务在执行快照读操作时 访问记录的版本链的过程。这样使不同事务的 读-写写-读 操作并发执行,从而提升系统性能。

核心点在于 ReadView 的原理, READ COMMITTDREPEATABLE READ 这两个隔离级别的一个很大不同 就是生成ReadView的时机不同:

  • READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView
  • REPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复 使用这个ReadView就好了。
image-20220715141413135

通过MVCC我们可以解决:

image-20220715141515370

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

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

相关文章

数据库I (SELECT语句)

目录 一、写在前面 1.0 内容概览 1.1 SQL 分类 1.2 SQL语言的规则与规范 1.2.1 基本规则 1.2.2 SQL大小写规范 (建议遵守) 1.3 注释 1.4 命名规则(暂时了解) 二、基本的SELECT语句 2.0 SELECT... 2.1 SELECT ... FROM…

HTML5 Web Workers

HTML5 Web Workers web worker 是运行在后台的 JavaScript,不会影响页面的性能,更好的解释是,你可以使用web worker提供的一种简单的方法来为web内容在后台线程中运行脚本,这些线程在执行任务的过程中并不会干扰用户界面&#xff…

Java反射复习

Java反射复习1.动态代理2.创建动态代理3.反射4.获取Class对象5. 反射获取构造方法6. 获取成员变量7. 获取成员变量并获取值和修改值8.获取成员方法9. 获取成员方法并运行10. 面试题:11. 练习泛型擦除12 练习:修改字符串的内容13 练习,反射和配…

【新2023Q2模拟题JAVA】华为OD机试 - 寻找密码

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:寻找密码 题目 小王在进行游…

Nginx 负载均衡及其高可用

优质博文:IT-BLOG-CN 负载均衡(Load Balance): 意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其他任务服务器等,从而共同完成工作任务。负载均衡建立在现有的网络…

面试官问 : SimpleDateFormat 不是线程安全的,你有了解过吗?

前言 金三银四又有战况: 我们的看官,不能白白牺牲! 现在,立刻,马上,跟我开始复现 ! 开始看我源码分析! 开始了解怎么解决! 正文 复现代码 多线程操作使用SimpleDateForma…

ChatGPT的平替来了?一文总结 ChatGPT 的开源平替,你值得拥有

文章目录【AIGC精选】总结 ChatGPT 的开源平替,你值得拥有1.斯坦福发布 Alpaca 7B,性能匹敌 GPT-3.52.弥补斯坦福 Alpaca 中文短板,中文大模型 BELLE 开源3.国产AI大模型 ChatGLM-6B 开启内测4.中文 Alpaca 模型 Luotuo 开源5. ChatGPT 最强竞…

计算机网络第一章(概述)【湖科大教书匠】

1. 各种网络 网络(Network)由若干**结点(Node)和连接这些结点的链路(Link)**组成多个网络还可以通过路由器互连起来,这样就构成了一个覆盖范围更大的网络,即互联网(互连网)。因此,互联网是"网络的网络(Network of Networks)"**因特…

Mybatis的二级缓存

缓存的概述和分类 概述 缓存就是一块内存空间.保存临时数据 为什么使用缓存 将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取的时候 ,直接从缓存中获取,可以减少和数据库交互的次数,这样可以提升程序的性能! 缓存的适用情况 …

38-二叉树练习-LeetCode145二叉树的后序遍历

题目 给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。 示例 1: 输入:root [1,null,2,3] 输出:[3,2,1] 示例 2: 输入:root [] 输出:[] 示例 3: 输入:ro…

让chatGPT当我的老师如何? 通过和chatGPT交互式学习,了解在ES中,一条JSON数据是如何写到磁盘上的

最近一直有一个问题,如鲠在喉。争取早一天解决,早一天踏踏实实的睡觉。 问题是:在ES中,一条JSON数据是如何写入到磁盘上的? 如何解决这个问题?我想到了chatGPT,还有lucene的学习资料。这篇文章&…

【机器学习】决策树(理论)

决策树(理论) 目录一、何为决策树1、决策树的组成2、决策树的构建二、熵1、熵的作用2、熵的定义3、熵的计算4、条件熵的引入5、条件熵的计算三、划分选择1、信息增益( ID3 算法选用的评估标准)2、信息增益率( C4.5 算法…

DetectGPT:使用概率曲率的零样本机器生成文本检测

DetectGPT的目的是确定一段文本是否由特定的llm生成,例如GPT-3。为了对段落 x 进行分类,DetectGPT 首先使用通用的预训练模型(例如 T5)对段落 ~xi 生成较小的扰动。然后DetectGPT将原始样本x的对数概率与每个扰动样本~xi进行比较。…

Springboot 多线程分批切割处理 大数据量List集合 ,实用示例

前言 哲学提问镇贴: 不了解异步怎么使用的看官, 可阅: SpringBoot 最简单的使用异步线程案例 Async_小目标青年的博客-CSDN博客 Springboot Async异步扩展使用 结合 CompletableFuture_小目标青年的博客-CSDN博客 想了解更多关于批量list处…

25- 卷积神经网络(CNN)原理 (TensorFlow系列) (深度学习)

知识要点 卷积神经网络的几个主要结构: 卷积层(Convolutions): Valid :不填充,也就是最终大小为卷积后的大小. Same:输出大小与原图大小一致,那么N ​变成了​N2P. padding-零填充. 池化层(Subsampli…

《程序员面试金典(第6版)》面试题 08.08. 有重复字符串的排列组合(回溯算法,全排列问题)C++

题目描述 有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。 示例1: 输入:S “qqe” 输出:[“eqq”,“qeq”,“qqe”] 示例2: 输入:S “ab” 输出:[“ab”, “ba”] 提示: 字符都是英文字母。…

Mybatis持久层框架 | Lombok搭建

💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! Lombok Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能。不需要再写getter、setter或equals方法,只要…

自然语言大模型介绍

1 简介 最近一直被大语言模型刷屏。本文是周末技术分享会的提纲,总结了一些自然语言模型相关的重要技术,以及各个主流公司的研究方向和进展,和大家共同学习。 2 Transformer 目前的大模型基本都是Transformer及其变种。本部分将介绍Transf…

【WEB前端进阶之路】 HTML 全路线学习知识点梳理(上)

前言 HTML 是一切Web开发的基础,本文专门为小白整理,针对前端零基础的朋友们,手把手教你学习HTML,让你轻松迈入WEB开发的行列。 首先,感谢 橙子_ 在HTML学习以及本文编写过程中对我的帮助。 文章目录前言一.HTML简介1.…

【NLP经典论文阅读】Efficient Estimation of Word Representations in Vector Space(附代码)

❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…
最新文章