MySQL中的MVCC底层原理解读

2025-02-08 16:50

本文主要是介绍MySQL中的MVCC底层原理解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《MySQL中的MVCC底层原理解读》本文详细介绍了MySQL中的多版本并发控制(MVCC)机制,包括版本链、ReadView以及在不同事务隔离级别下MVCC的工作原理,通过一个具体的示例演示了在可重...

简介

MVCC(Multi-Version Concurrency ContChina编程rol)多版本并发控制,是用来在数据库中控制并发的方法,实现对数据库的并发访问用的。

mysql中,MVCC只在读取已提交(Read Committed)可重复读(Repeatable Read)两个事务级别下有效。其是通过Undo日志中的版本链ReadView一致性视图来实现的。

MVCC就是在多个事务同时存在时,SELECT语句找寻到具体是版本链上的哪个版本,然后在找到的版本上返回其中所记录的数据的过程。

首先需要知道的是,在MySQL中,会默认为我们的表后面添加三个隐藏字段:

  • DB_ROW_ID:行ID,MySQL的B+树索引特性要求每个表必须要有一个主键。如果没有设置的话,会自动寻找第一个不包含NULL的唯一索引列作为主键。如果还是找不到,就会在这个DB_ROW_ID上自动生成一个唯一值,以此来当作主键(该列和MVCC的关系不大);
  • DB_TRX_ID:事务ID,记录的是当前事务在做INSERT或UPDAandroidTE语句操作时的事务ID(DELETE语句被当做是UPDATE语句的特殊情况,后面会进行说明);
  • DB_ROLL_PTR:回滚指针,通过它可以将不同的版本串联起来,形成版本链。相当于链表的next指针。

(注意,添加的隐藏字段并不是很多人认为的创建时间和删除时间,同时在MySQL中MVCC的实现也不是通过什么快照来实现的。之所以有这种说法可能是源自于《高性能MySQL》一书中对MySQL中MVCC的错误结论,然后就人云亦云传开了(注意,我这里一直强调的是MySQL中MVCC的实现,是因为在不同的数据库中可能会有不同的实现)。所以说看源码和看官方文档才是最权威的解释)

ReadView

ReadView一致性视图主要是由两部分组成:所有未提交事务的ID数组已经创建的最大事务ID组成(实际上ReadView还有其他的字段,但不影响这里对MVCC的讲解)。

比如:[100,200],300。事务100和200是当前未提交的事务,而事务300是当前创建的最大事务(已经提交了)。

当执行SELECT语句的时候会创建ReadView,但是在读取已提交和可重复读两个事务级别下,生成ReadView的策略是不一样的:读取已提交级别是每执行一次SELECT语句就会重新生成一份ReadView,而可重复读级别是只会在第一次SELECT语句执行的时候会生成一份,后续的SELECT语句会沿用之前生成的ReadView(即使后面有更新语句的话,也会继续沿用)。

版本链

所有版本的数据都只会存一份,然后通过回滚指针连接起来,之后就是通过一定的规则找到具体是哪个版本上的数据就行了。

假设现在有一张account表,其中有id和name两个字段,那么版本链的示意图如下:

MySQL中的MVCC底层原理解读

而具体版本链的比对规则如下,首先从版本链中拿出最上面第一个版本的事务ID开始逐个往下进行比对:

MySQL中的MVCC底层原理解读

(其中min_id指向ReadView中未提交事务数组中的最小事务ID,而max_id指向ReadView中的已经创建的最大事务ID)

如果落在绿色区间(DB_TRX_ID < min_id):这个版本比min_id还小(事务ID是从小往大顺序生成的),说明这个版本在SELECT之前就已经提交了,所以这个数据是可见的。或者(这里是短路或,前面条件不满足才会判断后面这个条件)这个版本的事务本身就是当前SELECT语句所在事务的话,也是一样可见的;

如果落在红色区间(DB_TRX_ID > max_id):表示这个版本是由将来启动的事务来生成的,当前还未开始,那么是不可见的;

如果落在黄色区间(min_id <= DB_TRX_ID <= max_id):这个时http://www.chinasem.cn候就需要再判断两种情况:

  • 如果这个版本的事务ID在ReadView的未提交事务数组中,表示这个版本是由还未提交的事务生成的,那么就是不可见的;
  • 如果这个版本的事务ID不在ReadView的未提交事务数组中,表示这个版本是已经提交了的事务生成的,那么是可见的。

如果在上述的判断中发现当前版本是不可见的,那么就继续从版本链中通过回滚指针拿取下一个版本来进行上述的判断。

演示过程

下面通过一个示例来具体演示MVCC的执行过程(假设是在可重复读事务级别下),当前account表中已经有了一条初始数据(id=1,name=monkey):

Transaction 100Transaction 200Transaction 300无事务ID无事务ID
1begin;begin;begin;begin;begin;
2UPDATE test SET a='1' WHERE id = 1;
3UPDATE test SET a='2' WHERE id = 2;
4UPDATE account SET name = 'monkey301' WHERE id = 1;
5commit;
6SELECT name FROM account WHERE id = 1;
7UPDATE account SET name = 'monkey101' WHERE id = 1;
8UPDATE account SET name = 'monkey102' WHERE id = 1;
9SELECT name FROM account WHERE id = 1;
10commit;UPDATE account SET name = 'monkey201' WHERE id = 1;
11UPDATE account SET name = 'monkey202' WHERE id = 1;
12SELECT name FROM account WHERE id = 1;SELECT name FROM account WHERE id = 1;
13commit;

从左往右分别是五个事务,从上到下是时刻点。其中在第2和3时刻点中事务100和事务200(这里两个事务之间相差100只是为了更加方便去看,正常来说下个事务的ID是以+1的方式来创建的)分别执行了一条UPDATE语句,这两条语句并无实际作用,只是为了生成事务ID的,所以在下面的MVCC执行过程中就不分析这两条语句所带来的影响了,我们只研究account表。而其中最后两个事务,我是注明没有事务ID的。因为事务ID是执行一条更新操作(增删改)的语句后才会生成(这也是事务100和事务200要先执行一条更新语句的意义),并不是开启事务的时候就会生成。最后两个事务中可以看到就是执行了一些SELECT语句而已,所以它们并没有事务ID。

首先来看一下初始状态时的版本链和ReadView(ReadView此时还未生成):

MySQL中的MVCC底层原理解读

其中事务1在account表中创建了一条初始数据。

  • 之后在第1时刻点,五个事务分别开启了事务(如上所说,这个时候还没有生成事务ID)。
  • 在第2时刻点,第一个事务执行了一条UPDATE语句,生成了事务ID为100。
  • 在第3时刻点,第二个事务执行了一条UPDATE语句,生成了事务ID为200。
  • 在第4时刻点,第三个事务执行了一条UPDATE语句,将account表中id为1的name改为了monkey301。同时生成了事务ID为300。
  • 在第5时刻点,事务300也就是上面的事务执行了commit操作。
  • 在第6时刻点,第四个事务执行了一条SELECT语句,想要查询一下当前id为1的数据(如上所说,该事务没有生成事务ID)。

此时的版本链和ReadView如下:

MySQL中的MVCC底层原理解读

因为在第5时刻点,事务300已经commit了,所以ReadView的未提交事务数组中不包含它。此时根据上面所说的比对规则,拿版本链中的第一个版本的事务ID为300进行比对,首先当前这条SELECT语句没有在事务300中进行查询,然后发现是落在黄色区间,而且事务300也没有在ReadView的未提交事务数组中,所以是可见的。即此时在第6时刻点,第四个事务所查找到的结果是monkey301。

  • 在第7时刻点,事务100执行了一条UPDATE语句,将account表中id为1的name改为了monkey101。
  • 在第8时刻点,事务100又执行了一条UPDATE语句,将account表中id为1的name改为了monkey102。
  • 在第9时刻点,第四个事务执行了一条SELECT语句,想要查询一下当前id为1的数据。

此时的版本链和ReadView如下:

MySQL中的MVCC底层原理解读

注意,因为当前是在可重复读的事务级别下,所以此时的ReadView沿用了在第6时刻点生成的ReadView(如果是在读取已提交的事务级别下,此时就会重新生成一份ReadView了)。然后根据上面所说的比对规则,拿版本链中的第一个版本的事务ID为100进行比对,首先当前这条SELECT语句没有在事务100中进行查询,然后发现是落在黄色区间,而且事务100是在ReadView的未提交事务数组中,所以是不可见的。此时通过回滚指针拿取下一个版本,发现事务ID仍然为100,经过分析后还是不可见的。此时又拿取下一个版本:事务ID为300进行比对,首先当前这条SELECT语句没有在事务300中进行查询,然后发现是落在黄色区间,但是事务300没有在ReadView的未提交事务数组中,所以是可见的。即此时在第9时刻点,第四个事务所查找到的结果仍然是monkey301(这也就是可重复读的含义)。

  • 在第10时刻点,事务100commit提交事务了。同时事务200执行了一条UPDATE语句,将account表中id为1的name改为了monkey201。
  • 在第11时刻点,事务200又执行了一条UPDATE语句,将account表中id为1的name改为了monkey202。
  • 在第12时刻点,第四个事务执行了一条SELECT语句,想要查询一下当前id为1的数据。

此时的版本链和ReadView如下:

MySQL中的MVCC底层原理解读

跟第9时刻点一样,在可重复读的事务级别下,ReadView沿用了在第6时刻点生成的ReadView。然后根据上面所说的比对规则,拿版本链中的第一个版本的事务ID为200进行比对,首先当前这条SELECT语句没有在事务200中进行查询,然后发现是落在黄色区间,而且事务200是在ReadView的未提交事务数组中,所以是不可见的。此时通过回滚指针拿取下一个版本,发现事务ID仍然为200,经过分析后还是不可见的。此时又拿取下一个版本:事务ID为100进行比对,首先当前这条SELECT语句没有在事务100中进行查询,然后发现是落在黄色区间内,同时在ReadView的未提交数组中,所以依然是不可见的。此时又拿取下一个版本,发现事务ID仍然为100,经过分析后还是不可见的。此时再拿取下一个版本:事务ID为300进行比对,首先当前这条SELECT语句没有在事务300中进行查询,然后发现是落在黄色区间,但是事务300没有在ReadView的未提交事务数组中,所以是可见的。即此时在第12时刻点,第四个事务所查找到的结果仍然是monkey301。

同时在第12时刻点,第五个事务执行了一条SELECT语句,想要查询一下当前id为1的数据。

此时的版本链和ReadView如下:

MySQL中的MVCC底层原理解读

注意,此时第五个事务因为是该事务内的第一条SELECT语句,所以会重新生成在当前情况下的ReadView,即上图中所示的内容。可以看到,和第四个事务生成的ReadView并不一样,因为在之前的第10时刻点,事务100已经提交事务了。然后根据上面所说的比对规则,拿版本链中的第一个版本的事务ID为200进行比对,首先当前这条SELECT语句没有在事务200中进行查询,然后发现是落在黄色区间,而且事务200是在ReadView的未提交事务数组中,所以是不可见的。此时通过回滚指针拿取下一个版本,发现事务ID仍然为200,经过分析后还是不可见的。此时又拿取下一个版本:事务ID为100进行比对,发现是在绿色区间,所以是可见的。即此时在第12时刻点,第五个事务所查找到的结果是monkey102(可以看到,即使是同一条SELEjsCT语句,在不同的事务中,查询出来的结果也可能是不同的,究其原因就是因为ReadView的不同)。

  • 在第13时刻点,事务200执行了commit操作,整段分析过程结束。

以上演示的就是MVCC的具体执行过程,在多个事务下,版本链和ReadView是如何配js合进行查找的。上面还遗漏了一种情况没有进行说明,就是如果是DELETE语句的话,也会在版本链上将最新的数据插入一份,然后将事务ID赋值为当前进行删除操作的事务ID。但是同时会在该条记录的信息头(record header)里面的deleted_flag标记位置为true,以此来表示当前记录已经被删除。所以如果经过版本比对后发现找到的版本上的deleted_flag标记位为true的话,那么也不会返回,而是继续寻找下一个。

另外,如果当前事务执行rollback回滚的话,会把版本链中属于该事务的所有版本都删除掉。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于MySQL中的MVCC底层原理解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153353

相关文章

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

Mysql中设计数据表的过程解析

《Mysql中设计数据表的过程解析》数据库约束通过NOTNULL、UNIQUE、DEFAULT、主键和外键等规则保障数据完整性,自动校验数据,减少人工错误,提升数据一致性和业务逻辑严谨性,本文介绍My... 目录1.引言2.NOT NULL——制定某列不可以存储NULL值2.UNIQUE——保证某一列的每一

解密SQL查询语句执行的过程

《解密SQL查询语句执行的过程》文章讲解了SQL语句的执行流程,涵盖解析、优化、执行三个核心阶段,并介绍执行计划查看方法EXPLAIN,同时提出性能优化技巧如合理使用索引、避免SELECT*、JOIN... 目录1. SQL语句的基本结构2. SQL语句的执行过程3. SQL语句的执行计划4. 常见的性能优

Spring Security 单点登录与自动登录机制的实现原理

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token... 目录一、核心概念解析1.1 单点登录(SSO)1.2 自动登录(Remember Me)二、代码分析三、

SQL Server 中的 WITH (NOLOCK) 示例详解

《SQLServer中的WITH(NOLOCK)示例详解》SQLServer中的WITH(NOLOCK)是一种表提示,等同于READUNCOMMITTED隔离级别,允许查询在不获取共享锁的情... 目录SQL Server 中的 WITH (NOLOCK) 详解一、WITH (NOLOCK) 的本质二、工作

MySQL 强制使用特定索引的操作

《MySQL强制使用特定索引的操作》MySQL可通过FORCEINDEX、USEINDEX等语法强制查询使用特定索引,但优化器可能不采纳,需结合EXPLAIN分析执行计划,避免性能下降,注意版本差异... 目录1. 使用FORCE INDEX语法2. 使用USE INDEX语法3. 使用IGNORE IND

SQL Server安装时候没有中文选项的解决方法

《SQLServer安装时候没有中文选项的解决方法》用户安装SQLServer时界面全英文,无中文选项,通过修改安装设置中的国家或地区为中文中国,重启安装程序后界面恢复中文,解决了问题,对SQLSe... 你是不是在安装SQL Server时候发现安装界面和别人不同,并且无论如何都没有中文选项?这个问题也

2025版mysql8.0.41 winx64 手动安装详细教程

《2025版mysql8.0.41winx64手动安装详细教程》本文指导Windows系统下MySQL安装配置,包含解压、设置环境变量、my.ini配置、初始化密码获取、服务安装与手动启动等步骤,... 目录一、下载安装包二、配置环境变量三、安装配置四、启动 mysql 服务,修改密码一、下载安装包安装地