MVCC实现原理之ReadView(一步到位)(上)

简介: MVCC实现原理之ReadView(一步到位)

前置知识点

MVCC 的实现依赖于:隐藏字段、Undo Log、Read View。

什么是ReadView

Read View是一个数据库的内部快照,该快照被用于InnoDB存储引擎中的MVCC机制。简单点说,Read View就是一个快照,保存着数据库某个时刻的数据信息。Read View会根据事务的隔离级别决定在某个事务开始时,该事务能看到什么信息。就是说通过Read View,事务可以知道此时此刻能看到哪个版本的数据记录(有可能不是最新版本的,也有可能是最新版本的)。可重复读、读已提交、读未提交,这几个隔离级别都会使用Read View。

设计思路

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


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


使用 READ COMMITTED 和 REPEATABLE 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。


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时生成该版本的事务已经被提交,该版本可以被访问。


MVCC整体操作流程


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


1. 首先获取事务自己的版本号,也就是事务 ID;


2. 获取 ReadView;


3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;


4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;


5. 最后返回符合规则的数据。 在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次Read View。


如表所示:


334da40626754d20a6de825fcdb5a05a.png

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


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

2217e46b0ebe475cb1a5b3033bbfdd36.png

举例说明

READ COMMITTED隔离级别下

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

现在有两个 事务id 分别为 10 、 20 的事务在执行:

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

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

ddd1f91d737c4518accaa2099665b53b.png

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

# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'

之后,我们把 事务id 为 10 的事务提交一下:  

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

然后再到 事务id 为 20 的事务中更新一下表 student 中 id 为 1 的记录:

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

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


3dcf5021ea854a4896f1e0b4f6468365.png

然后再到刚才使用 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的值为'王五'


相关文章
|
存储 SQL 负载均衡
列式存储引擎分析比对
列式存储具有高压缩率、利于列裁剪、以及高CPU计算效率(Cache Friendly)等特点,是分析型业务场景所选择的主流数据存储方案。 本文介绍了工业界一些常见的面向OLAP或HTAP场景数据库的列存存储引擎设计思路,并进行了总结和对比。
4413 3
|
SQL 移动开发 算法
MySQL 8.0.23 Hypergraph Join Optimizer代码详解
MySQL Join MySQL本身没有常规意义上的执行计划,一般情况就是通过JOIN和QEP_TAB这两个结构组成。QEP_TAB 的全称是Query Execution Plan Table,这个“Table“可以是物理表、内存表、常量表、子查询的结果表等等。作为整个单独JOIN执行计划载体之前还承担着整个执行路径的调用和流转,但是从8.0.20后,全面的生成了独立的
2207 0
MySQL 8.0.23 Hypergraph Join Optimizer代码详解
基于springboot的养老院管理系统
随着人口老龄化加剧,传统养老院管理效率低下,亟需信息化升级。本文基于Java、Spring Boot、Vue等技术构建智慧养老系统,结合MySQL与MyBatis实现数据高效管理,提升服务精准性与运营效率,推动养老服务向智能化、现代化发展。
|
人工智能 自然语言处理 Java
Spring 集成 DeepSeek 的 3大方法(史上最全)
DeepSeek 的 API 接口和 OpenAI 是兼容的。我们可以自定义 http client,按照 OpenAI 的rest 接口格式,去访问 DeepSeek。自定义 Client 集成DeepSeek ,可以通过以下步骤实现。步骤 1:准备工作访问 DeepSeek 的开发者平台,注册并获取 API 密钥。DeepSeek 提供了与 OpenAI 兼容的 API 端点(例如),确保你已获取正确的 API 地址。
Spring 集成 DeepSeek 的 3大方法(史上最全)
|
SQL 安全 前端开发
如何做好研发FO角色?
如何做好研发FO角色?角色定位包含哪些能力要求和权利责任,项目过程中包含哪些核心里程碑节点以及交付物,FO对于项目过程管理需要具备哪些把控原则,针对风险和问题的治理手段和干预策略,协作过程的核心动作
2496 0
如何做好研发FO角色?
|
安全 Java 数据安全/隐私保护
Spring Security 6.x 一文快速搞懂配置原理
本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。
1358 5
Spring Security 6.x 一文快速搞懂配置原理
|
前端开发 JavaScript 测试技术
107-Django开发医院管理系统(医生-患者-医院管理员)
该内容描述了一个基于Python和Django的医疗管理系统的开发步骤。主要涵盖以下关键点: 1. 环境搭建:安装Python和Django,创建项目和应用。 2. 数据库模型:设计用户、医生、患者、发票和预约等模型。 3. 用户认证和权限:使用Django内置系统,创建自定义角色和权限。 4. 视图和模板:处理用户请求,渲染HTML页面,包括医生列表、患者注册、管理界面等。 5. 表单处理:创建表单处理用户输入,验证数据并保存到数据库。 6. PDF生成:利用xhtml2pdf库生成治疗发票的PDF。 7. 路由配置:设置URL路由以连接视图。
620 7
107-Django开发医院管理系统(医生-患者-医院管理员)
|
小程序 前端开发 测试技术
开发“校园帮”小程序:从构思到上线的完整指南
开发“校园帮”小程序:从构思到上线的完整指南
1018 2
|
SQL 关系型数据库 API
Star 4.7k!高效SQL Parser!纯Python开发!自称目前最快的纯Python SQL解析器!
Star 4.7k!高效SQL Parser!纯Python开发!自称目前最快的纯Python SQL解析器!
1465 1
|
Web App开发 安全 测试技术
自动化测试中的Python魔法:使用Selenium和pytest框架
【8月更文挑战第31天】 在软件开发的海洋中,自动化测试是确保航行安全的灯塔。本文将带你探索如何利用Python语言结合Selenium和pytest框架,搭建一套高效的自动化测试体系。我们将从基础设置讲起,逐步深入到编写测试用例,最后通过一个实战案例来展示如何在实际项目中运用这些工具。文章旨在为读者提供一套清晰的自动化测试解决方案,让你的开发之旅更加顺畅。
2039 0