MVCC在Mysql中的运用

这篇具有很好参考价值的文章主要介绍了MVCC在Mysql中的运用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

MVCC到底是个啥?

定义: 多版本并发控制,字面理解,在并发过程中利用多个版本进行合理控制(反正我就是从字面这么理解),很明显,这个东西是个抽象的概念,事实也是如此。它主要是出现在一些数据管理软件中。
维持一个数据的多个版本,使得读写操作没有冲突。

  • 为什么会有这个东西呢?

我们都知道,数据管理程序提供的功能就是对数据的查询和修改,但是读写过程中如何怎么解决冲突问题呢,为了维护数据的一致性且保持较高的性能,到即使有读写冲突时,也能做到不加锁,非阻塞并发读, MVCC 这种并发控制算法就出现了。

MVCC基础原理

MVCC 是允许一个对象的多个版本同时存在。也就是说,他拥有 “当前” 版本和一个或多个以前的版本。当你在获取版本时可以根据需要使用它的不同版本来解决你面对的问题。在此运行期间, “作者” 可以创建和发布新的对象版本,该版本将成为对象的最新版本, “读者” 依旧也可以使用之前的版本。

到底使用哪些版本提供给 “读者” 呢?这个就和你的真实需求相关了,例如 Mysql 中的隔离级别,不同级别对相同并发操作后的结果看到不一致,我们可以根据需求来合理展示数据对象的版本,在不同隔离级别上实现不同的效果。

既然知道了 MVCC 的抽象,我们再去看看它的实现, MVCC 被利拥到很多的数据管理程序上,无疑证明他是一个很好的设计思路,例如在 Mysql Innodb引擎 、 Etcd存储 、 PostgreSQLoracle 等等,我们就以 Innodb 引擎为例,观察一下他是如何实现 MVCC

Innodb引擎MVCC探究

Innodb 引擎中,我们需要先了解一些基础概念: undolog版本链 ReadView

undolog

我们知道,事务是具有原子性的,但是当系统故障,或者手动回滚时,如何保证这次提交的数据全部进行恢复呢,有时候都可能事务只执行到一半,我们要保证它和原来一样,这个事务看起来什么都没有做,举一个生活中的例子:如象棋,当下错子的时候可以申请悔棋, 悔棋 就是一种回滚操作,实际上就是之前执行的操作再次执行一次逆向操作,数据库中的回滚跟 悔棋 差不多,你插入一条记录,回滚操作日志对应的就是删除这个记录;你更新了一条记录,回滚操作对应的就是把该记录更新为旧值;你删除一条记录,对应回滚操作就是插入。

这些为了回滚记录的日志称为撤销日志 undolog ,在真实的 InnoDB 中, undo日志 其实并不像我们上边所说的那么简单,不同类型的操作产生的 undo日志 的格式也是不同的,不过先暂时把这些容易让人脑子糊的具体细节放一放,注意我们文章的重点,接着往下看。

版本链

版本链式用来存储该数据行的历史,每次对记录进行更新后,都会将旧值放到一条 undo日志 中,形成该记录的一个旧版本,随着更新次数的增加,所有版本都会被数据行的 roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录的最新值。另外,每个版本还包含生成该版本的 事务ID ,大概逻辑如下图所示:

MVCC在Mysql中的运用

ReadView

RCRR 隔离级别中(READ UNCOMMITTED、SERIALIZABLE 是没有使用的Read View的),都必须保证已经读到已经提交了的事务修改过的记录,也就是说假设另一个记录已经修改了但是尚未提交,是不能直接读取最新版本的记录,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。Read View 这个概念就是为了解决这个问题,它包含了4个内容

  1. m_ids 生成 ReadView 时当前系统中活跃的读写事务ID列表
  2. min_trx_id 生成 ReadView 时当前系统中活跃事务最小的事务ID,即 m_ids 中的最小值
  3. max_trx_id 生成 ReadView 时系统中应该分配给下一个事务ID
  4. creator_trx_id 生成 ReadView 时的事务ID只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。
    有了这个 ReadView ,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
  • 如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值大于或等于 ReadView 中的max_trx_id值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_idmax_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边步骤判断可见性。依次类推,直到版本链中的最后一个版本。如果最后一个版本都不可见的话,那就意味着该记录对该事务完全不可见,查询结果也就不包含该记录。

而生成 ReadView 的时机不同也直接影响了查询操作的结果,在 Mysql 中, RCRR 隔离级别最大的区别就是生成 ReadView 的时机不同。

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

比方说现在系统里有两个事务id分别为100、200的事务在执行:

# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;
# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

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

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

MVCC在Mysql中的运用

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

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

# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

这个SELECT1的执行过程如下:

  • 在执行SELECT语句时会先生成一个ReadViewReadViewm_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’张飞’,该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’关羽’,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’刘备’的记录。

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


# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;

COMMIT;

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


# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

UPDATE hero SET name = '赵云' WHERE number = 1;

UPDATE hero SET name = '诸葛亮' WHERE number = 1;

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

MVCC在Mysql中的运用

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


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

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'张飞'

这个SELECT2的执行过程如下:

  • 在执行SELECT语句时会又会单独生成一个ReadView,该ReadViewm_ids列表的内容就是[200](事务id为100的那个事务已经提交了,所以再次生成快照时就没有它了),min_trx_id为200,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’诸葛亮’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’赵云’,该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’张飞’,该版本的trx_id值为100,小于ReadView中的min_trx_id值200,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’张飞’的记录。

以此类推,如果之后事务id为200的记录也提交了,再次在使用READ COMMITTED隔离级别的事务中查询表hero中number值为1的记录时,得到的结果就是’诸葛亮’了,具体流程我们就不分析了。总结一下就是:使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。

REPEATABLE READ —— 在第一次读取数据时生成一个ReadView,在一个事务中保证多个查询结果一致

对于使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了。我们还是用例子看一下是什么效果。

比方说现在系统里有两个事务id分别为100、200的事务在执行:

# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;
# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

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

MVCC在Mysql中的运用

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

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

# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

这个SELECT1的执行过程如下:

  • 在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’张飞’,该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’关羽’,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’刘备’的记录。

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

# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;

COMMIT;

然后再到事务id为200的事务中更新一下表hero中number为1的记录:


# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

UPDATE hero SET name = '赵云' WHERE number = 1;

UPDATE hero SET name = '诸葛亮' WHERE number = 1;

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

MVCC在Mysql中的运用

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


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

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值仍为'刘备'

这个SELECT2的执行过程如下:

  • 因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’诸葛亮’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’赵云’,该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’张飞’,该版本的trx_id值为100,而m_ids列表中是包含值为100的事务id的,所以该版本也不符合要求,同理下一个列name的内容是’关羽’的版本也不符合要求。继续跳到下一个版本。
  • 下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为’刘备’的记录。

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

总结一下

看了上面的 undolog版本链ReadView 后,我们大致也有了一些想法, ReadView 主要是对记录的历史进行可见性规则制定和规则校验,而记录的历史则是采用链表结构存储在 undolog 个,三者结合解决了 MVCC 中的读写问题。而 mysql 中所谓的 MVCC 指的就是在使用 RC 和 RR 两种隔离级别的事务在执行普通 SELECT 操作时访问版本链的过程,这样子可以使不同事务的读写,写写操作并发执行,从而提升系统性能,而他们最大的不同点就在于生成 ReadView 的时机不同

References

[1] MySQL 是怎样运行的:从根儿上理解 MySQL: https://juejin.cn/book/6844733769996304392/section/6844733770071801870

参考:憧憬在 aoppp.com发布文章来源地址https://www.toymoban.com/news/detail-474731.html

到了这里,关于MVCC在Mysql中的运用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MySQL多版本并发控制mvcc原理浅析

    1.mvcc简介 1.1mvcc定义 mvcc(Multi Version Concurrency Control),多版本并发控制,是一种数据库的并发控制机制。它用于管理事务并发执行时对数据的访问和修改,保证在多个事务同时对数据库进行读写操作,不会出现数据不一致或丢失的情况 1.2mvcc解决的问题 当多个事务同时访问数据

    2024年04月25日
    浏览(36)
  • 【MySQL】事务之MVCC(多版本并发控制)

    读-读 :不存在任何问题,也不需要并发控制 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读 写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失 多版本并发控制 ( MVCC )是一种用来解决 读

    2024年02月14日
    浏览(46)
  • 【MySQL高级篇笔记-多版本并发控制MVCC(下) 】

    此笔记为尚硅谷MySQL高级篇部分内容 目录 一、什么是MVCC 二、快照读与当前读 1、快照读  2、当前读 三、复习 1、再谈隔离级别 2、隐藏字段、Undo Log版本链 四、MVCC实现原理之ReadView  1、什么是ReadView  2、设计思路 3、ReadView的规则 4、MVCC整体操作流程 五、举例说明 1、READ

    2024年02月08日
    浏览(61)
  • ⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制

    个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ InnoDB逻辑存储结构 : 🚀 表空间(idb文件) :一个MySQL实例可以对应多个表空间,用于存储记录、索引等数

    2024年02月04日
    浏览(44)
  • MYSQL的多版本并发控制MVCC(Multi-Version Concurrency Control)

    MVCC 是一种用于数据库管理系统的并发控制技术,允许多个事务同时访问数据库,而不会导致读写冲突。也就是说在读写的时候,线程不用去争抢读写锁。因为加锁的过程比较耗性能。 当然很多时候还是必须的,不能避免,比如说,去ATM机取钱的时候,同时又在手机APP上进行

    2024年02月07日
    浏览(40)
  • Mysql--技术文档--MVCC(Multi-Version Concurrency Control | 多版本并发控制)

            MVCC(Multi-Version Concurrency Control)是一种并发控制机制,用于解决并发访问数据库时的数据一致性和隔离性问题。MVCC允许多个事务同时读取数据库的同一数据,而不会相互干扰或导致冲突。         在传统的并发控制机制中,如锁定机制,事务会对读取和写入

    2024年02月11日
    浏览(42)
  • 多版本并发控制MVCC

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

    2024年02月08日
    浏览(41)
  • MVCC并发版本控制之重点ReadView

    本文大部分来自 《MySQL是怎样运行的》,这里只是简单总结,用于各位回忆和复习。 对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(不知道的快去看《MySQL是怎样运行的》) trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事

    2024年02月09日
    浏览(36)
  • MVCC:多版本并发控制案例分析(二)

    (笔记总结自b站马士兵教育课程) 本文主要分析readview的案例。 readview:表示事务进行快照读操作的时候产生的读视图,在该事务进行快照读的那一刻会生成一个系统当前的快照,但是此时的快照不是数据的快照,而是事务相关信息的快照。 trx_list readview生成时刻当前系统活

    2024年02月08日
    浏览(40)
  • 深入解析MVCC:多版本并发控制的数据库之道

    目录 引言 一、什么是MVCC? 二、MVCC的实现原理 2.1版本号 2.1.1版本号的作用: 2.1.2版本号的组成: 2.1.3.示例 2.2事务id 2.2.1事务ID的作用: 2.2.2事务ID的生成: 2.2.3示例: 2.3 快照(Snapshot) 2.3.1快照的作用: 2.3.2快照的实现方式: 2.3.3示例: 2.4版本链(Version Chain) 2.4.1版本链

    2024年01月24日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包