首页 > 其他分享 >MVCC机制讲解

MVCC机制讲解

时间:2023-11-24 09:46:58浏览次数:37  
标签:事务 快照 MVCC trx 版本 讲解 机制 id

MVCC机制讲解



欢迎关注 MySQL 专栏 MySQL 历险记
强烈建议收藏本导航文【MySQL 历险记】MySQL 的核心特性汇总

前言

MySQL 中大名鼎鼎的 MVCC 机制想必大家都有所耳闻吧,虽然在平时 MySQL 使用过程中基本上用不到,但是面试中出场率十分高,而且作为架构师的你也是需要知道它的工作机制。那么你对 MVCC 机制了解多少呢?MVCC 机制是用来干嘛的呢?底层的工作原理是怎么样的呢?本文就带你一探究竟。

MVCC 机制是什么?

MVCC,英文全称 Multiversion Concurrency Control​​,多版本并发控制。简单理解,就是相当于给我们的 MySQL 数据库拍个“​快照​”,定格某个时刻数据库的状态。

那你可能问为什么要拍个“快照”,也就是 MVCC 机制?

还记得事务的一大特性就是隔离性,一共有 4 个隔离级别,读未提交,读已提交,可重复读,串行化。

MySQL InnoDB​​ 引擎的默认隔离级别可重复读为例,可重复读指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。

关于事务的基本特性请移步一文带你理解 MySQL 事务核心知识点

为了保证事务启动到结束整个生命周期看到的数据是一致的, 一般有两种方案:

  1. MySQL 对数据“读写,写写”的时候加锁,其他事务写这条数据时加上锁,其他事务读取的时候阻塞。实际上锁住一个记录,其他事务阻塞等待
  2. MySQL 可以对事务启动的时候,对数据库拍个“快照”,那么事务运行过程中读取都从这个快照读取,不也是保证数据一致么。

第一种方案存在明显的问题,加锁会引发阻塞,从而降低数据库性能,比如三个事务abc分别更新1 2,3三个记录,a想读取1,2,3个记录后再更新,为了保证不可重复读的隔离级别,a事务需要锁住记录1,2,3才能实现业务逻辑,此时事务b,c就阻塞了。而 MySQL 设计者们采用第二种,也就是大名鼎鼎的 MVCC​​,它不仅能够解决不可重复读,还一定程度解决幻读的问题,因为你整个数据库快照都有了,你就知道那个时刻的数据了。

虽然说 SQL 标准定义中可重复读隔离级别下会存在幻读的现象,但是不同的数据库厂商可以基于 SQL 标准下有不同的实现,那么不同隔离级别下发生的现象也会有出入,就拿 MySQL 的可重复读隔离级别就可以一定程度保证幻读。

小结一下:

MVCC 在 MySQL InnoDB​ 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突 ,做到即使有读写冲突时,也能做到不加锁(主要在这个场景下相比于加锁有不用加锁的优势) , 非阻塞并发读,而这个读指的就是快照读 , 而非当前读。

什么是快照读和当前读?

前面提到了快照读和当前读,这又有什么不一样呢,什么样的 sql 语句算是快照读,什么样的又算是当前读呢?

快照读

快照读又叫普通读,也就是利用 MVCC 机制读取快照中的数据。​不加锁的简单的 SELECT 都属于快照读​,比如这样:

SELECT * FROM user WHERE ...
  • 快照读是基于 MVCC 实现的,提高了并发的性能,降低开销
  • 大部分业务代码中的读取都属于快照读

当前读

当前读读取的是记录的最新版本,读取时会对读取的记录进行加锁, 其他事务就有可能阻塞。​加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:

SELECT * FROM user LOCK IN SHARE MODE; # 共享锁
SELECT * FROM user FOR UPDATE; # 排他锁
INSERT INTO user values ... # 排他锁
DELETE FROM user WHERE ... # 排他锁
UPDATE user SET ... # 排他锁
  • update、delete、insert​ ​语句虽然没有 select​​, 但是它们也会先进行读取,而且只能读取最新版本。
快照读 当前读
只有select
SELECT * FROM user LOCK IN SHARE MODE;共享锁
SELECT * FROM user FOR UPDATE; # 排他锁
INSERT INTO user values ... # 排他锁
DELETE FROM user WHERE ... # 排他锁
UPDATE user SET ... # 排他锁

MVCC 机制是咋工作的呢?

前面打个比方说 MVCC 机制相当于是基于整个数据库“拍了个快照”,这时,你会说这看上去不太现实啊。如果一个库有 100G,那么我启动一个事务,MySQL 就要保存 100G 的数据出来,这个过程得多慢啊,而且也很占用空间啊,根本就不能支持几个事务啊。别急,我们现在来讲解下 MVCC 机制是如何工作的。

数据的多个版本

首先 MySQL innoDB​ ​存储引擎需要支持一条数据可以保留多个历史版本。怎么保留呢?还记得事务日志 undo log​ ​吗?

undo log 保存了数据的各个历史版本,用于数据的回滚,保证事务的一致性。详情查看详解 MySQL 事务日志——undo log

对于使用 InnoDB​​ 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:

  • trx_id​,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id​​隐藏列里​;
  • roll_pointer​,每次对某条聚簇索引记录进行改动时,都会​把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录​,于是就可以通过它找到修改前的记录。

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id​。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。

如上图所示,针对 id=1​ ​的这条数据,都会将旧值放到一条 undo 日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer​​ 属性连接成一个链表,我们把这个链表称之为版本链​,根据版本链就可以找到这条数据历史的版本。

一致性视图 ReadView

利用 undo log ​日志我们已经保留下了数据的各个版本,那么现在关键的问题是要读取哪个版本的数据呢?

这时就需要用到 ReadView ​了,ReadView ​就是事务在使用 MVCC ​机制进行快照读操作时产生的一致性视图, 比如针对可重复读隔离级别,是在事务启动的时候,创建一个 ReadView​, 那 ReadView ​种都有哪些关键信息呢?

  • trx_ids​​: 指的是在创建 ReadView​​ 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表, ​ “活跃事务”指的就是,​启动了但还没提交的事务。
  • min_trx_id​​: 指的是在创建 ReadView​​ 时,​当前数据库中「活跃事务」中​事务 id 最小的事务,也就是 m_ids 的最小值。
  • max_trx_id​​:这个并不是 m_ids​​ 的最大值,而是创建 ReadView​​​时当前数据库中应该给下一个事务的 id 值,也就是​全局事务中最大的事务 id 值 + 1​;
  • creator_trx_id​​ :指的是创建该 ReadView​​ 的事务的事务 id, 只有在对表中的记录做改动时(执行 INSERT、DELETE、UPDATE​ ​这些语句时)才会为 事务分配事务 id,否则在一个只读事务中的事务 id 值都默认为 0。

对于当前事务的启动瞬间来说,读取的一个数据版本的 trx_id,有以下几种可能:

  • 如果被访问版本的 trx_id ​属性值与 ReadView ​中的 creator_trx_id​ 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。

  • 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;

  • 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;

  • 如果落在黄色部分,那就包括两种情况

  • 若 数据的 trx_id​ ​在 trx_ids​ ​数组中,表示这个版本是由还没提交的事务生成的,不可见, 去读取这条数据的历史版本,这条数据的历史版本中都包含了事务 id 信息,去查找第一个不在活跃事务数组的版本记录。

  • 若 数据的 trx_id ​不在 trx_ids ​数组中,表示这个版本是已经提交了的事务生成的,可见。

这种通过版本链 + 一致性视图 来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制),现在你明白 MySQL 如何实现了“秒级创建快照”的能力了吧。

还是不懂?举例说明

如果你对 MVCC 机制的整个流程还是比较模糊,我们现在举例来说明下。

比如 student ​表中有一个事务 id 为 8 的插入记录:

insert into student(id, name, class) values(1, '张三', '一班')

我们现在在 MySQL 的读已提交和可重复读隔离级别下,MVCC 机制的整个工作流程。

MySQL 中的读未提交和序列化并不需要 MVCC 机制,读未提交,直接读取别人未提交的数据,而序列化全程用加锁的方式,也用不上 MVCC, 大家体会下。

可重复读隔离级别下

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

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。

事务 10 事务 20 事务 30
beginUPDATE student SET name="李四" WHERE id=1;UPDATE student SET name="王五" WHERE id=1;
begin 更新了一些其他表的数据
beginSELECT * FROM student WHERE id = 1;

事务 10 和 20 均未提交,现在事务 30 执行 select​, 那么得到的结果是什么呢?

  1. 在执行 select ​语句时会先生成一个 ReadView​,ReadView 的 trx_ids ​列表的内容就是 [10, 20]​,min_trx_id ​为 10,max_trx_id ​为 21,creator_trx_id ​为 0。
  2. 然后从版本链中挑选可见的记录,从图中看出,最新版本的列 name 的内容是 '王五'​,该版本的 trx_id 值为 10,在 trx_ids ​列表内,所以不符合可见性要求,根据 roll_pointer ​跳到下一个版本。
  3. 下一个版本的列 name ​的内容是 '李四'​,该版本的 trx_id ​值也为 10,也在 trx_ids ​列表内,所以也不符合要求,继续跳到下一个版本。
  4. 下一个版本的列 name ​的内容是'张三​',该版本的 trx_id ​值为 8,小于 ReadView ​中的 min_trx_id ​值 10,说明已经提交了,那么最终返回 '张三'​。

读已提交隔离级别下

读已提交 READ COMMITTED ​是每次读取数据前都生成一个 ReadView​。基本的规则和流程与可重复读隔离级别一致,这里不做重复赘叙。

总结

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

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

如果本文对你有帮助的话,请留下一个赞吧

标签:事务,快照,MVCC,trx,版本,讲解,机制,id
From: https://www.cnblogs.com/hdld/p/mvcc-mechanism-explanation-zftgrw.html

相关文章

  • Redis深入理解-Socket连接建立流程以及文件事件处理机制
    RedisServer运行原理图Redis服务器中Socket网络建立以及文件事件模型一个redis单机,可以抗几百上千的并发,这里的并发指的就是同时可以有几百个client对这个redisserver发起请求,都需要去建立网络连接,同时间可能会有几百个redisclient通过socket和我们的redisserve......
  • poll机制
    一.参考网址1. 一文带你搞懂中断按键驱动程序之poll机制(超详细)2. Linux网络编程——I/O复用之poll函数......
  • 雅礼信奥 2023.11.22 习题课记录(讲解版)
    雅礼信奥\(2023.11.22\)习题课记录(讲解版)都是CF题,不如AT。剧情版后面会更。A-YarikandArray(CF1899C)dp题,作为学OI\(3\)年的萌新OIer,后面才想到dp真是太蒟蒻了,时间复杂度\(O(tn)\)。初始\(f_1=1\),其他为\(0\)。状态转移方程:\(\begin{cases}\text{if}&......
  • 数据库服务器开启内存大页优化及机制
    一、背景在一次Oracle数据库健康检查报告中,显示PageTables所占用内存过大,建议配置大页PageTables(页表):用于将内存的虚拟地址翻译成物理地址,随着内存地址分配得越来越多,这个需要从Linux分页了解起二、Linux分页在计算机操作系统中,内存分页是一种内存管理方案,也是现代操作系统......
  • TCP机制|确认应答、超时重传和连接机制
    TCP全称TransmissionControlProtocol,即传输控制协议,TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。 一、TCP协议格式16位源/目的端口号:发送方的端口号,接收方的端口号32位序号:TCP数据报携带的数据的起始序号32位确认序号:期待对方发送的数据是从哪一个序号开始......
  • 实例讲解:NodeJS 操作 Kafka
    本人是C#出身的程序员,c#很简单就能实现,有需要的可以加我私聊。但是就目前流行的开发语言,尤其是面向web方向应用的,我感觉就是Nodejs最简单了。下面介绍:本文将会介绍在windows环境下启动Kafka,并通过nodejs作为客户端,生产和消费消息。步骤一,Kafka需要java运行时,先安装配置java环境。下......
  • Python全局解释器锁GIL机制
    全局解释器锁GlobalInterpreterLock,CPython在解释器级别的一把锁,叫GIL全局解释器锁。程序编译成字节码,程序想跑多线程,但是GIL保证CPython进程中,同一时刻只能有一个线程执行字节码。所以,哪怕是在多CPU的情况下,即使每个线程恰好调度到了每个CPU上,有了这把大锁,同时只能有一个CPU......
  • 实例讲解C++连接各种数据库,包含SQL Server、MySQL、Oracle、ACCESS、SQLite 和 Postgr
     C++是一种通用的编程语言,可以使用不同的库和驱动程序来连接各种数据库。以下是一些示例代码,演示如何使用C++连接SQLServer、MySQL、Oracle、ACCESS、SQLite和PostgreSQL、MongoDB数据库。连接SQLServer数据库要使用C++连接SQLServer数据库,可以使用Micro......
  • 记录--用了那么久的Vue,你了解Vue的报错机制吗?
    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 Vue的5种处理Vue异常的方法相信大家对Vue都不陌生。在使用Vue的时候也会遇到报错,也会使用浏览器的F12来查看报错信息。但是你知道Vue是如何进行异常抛出的吗?vue是如何处理异常的呢?接下来和大家介绍介绍,Vue......
  • 回调函数用于通知机制
    相机SDK中一般有这样的回调:当帧采集完毕,自动调用回调函数。回调函数用于通知机制:当某一事件发生时,如果使用者注册过了回调函数,则会自动执行回调函数中的内容。网上很多回调函数的内容都是简单的使用下,没有太多关于通知机制的内容,于是找了一个案例//sdk.htypedefvoid(*REC_CA......