mvcc 订阅
Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。 展开全文
Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。
信息
避    免
使用锁
保    存
某个时间点上的数据快照
增    加
并发性
中文名
MVCC
被称为
多版本并发控制
MVCC产品简介
大多数的MySQL事务型存储引擎,如InnoDB,Falcon以及PBXT都在使用一种简单的行锁机制。事实上,他们都和另外一种用来增加并发性的被称为“多版本并发控制(MVCC)”的机制来一起使用。MVCC不只使用在MySQL中,Oracle、PostgreSQL,以及其他一些数据库系统也同样使用它。你可将MVCC看成行级别锁的一种妥协,它在许多情况下避免了使用锁,同时可以提供更小的开销。根据实现的不同,它可以允许非阻塞式读,在写操作进行时只锁定必要的记录。MVCC会保存某个时间点上的数据快照。这意味着事务可以看到一个一致的数据视图,不管他们需要跑多久。这同时也意味着不同的事务在同一个时间点看到的同一个表的数据可能是不同的。如果你从来没有过这种体验的话,可能理解起来比较抽象,但是随着慢慢地熟悉这种理解将会很容易。各个存储引擎对于MVCC的实现各不相同。这些不同中的一些包括乐观和悲观并发控制。我们将通过一个简化的InnoDB版本的行为来展示MVCC工作的一个侧面。InnoDB:通过为每一行记录添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。让我们来看看当隔离级别是REPEATABLE READ时这种策略是如何应用到特定的操作的:SELECT InnoDB必须每行数据来保证它符合两个条件:1、InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候。符合这两个条件的行可能会被当作查询结果而返回。INSERT:InnoDB为这个新行记录当前的系统版本号。DELETE:InnoDB将当前的系统版本号设置为这一行的删除ID。UPDATE:InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据 [1]  。
收起全文
精华内容
下载资源
问答
  • MySQL中RR级别下的MVCC

    2020-12-14 14:09:04
    众所周知MySQL中InnoDB的可重复读是通过MVCC实现的。 MVCC是由哪些元素构成? MVCC的读 又叫快照读,在MySQL中不可能把整个表的数据拷贝一份来提供这个快照。它通过  1、undolog  InnoDB增删数据时除了redo log外...
  • Fabric 1.4 源码分析 MVCC验证 读本节文档之前建议先查看[Fabric 1.4 源码分析 committer记账节点]章节。 1. MVCC简介 Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在...
  • MVCC

    2021-03-08 00:05:19
    MVCC 首先我们都知道,mysql中的读操作最容易出现的三大问题:脏读、幻读、不可重复读 脏读(一个事务读取到了另一个事务未提交的数据): 时间轴(可以自行把时间的流逝当做一条时间线) 事务A 事务B T1 开始...

    MVCC

    首先我们都知道,mysql中的读操作最容易出现的三大问题:脏读、幻读、不可重复读

    脏读(一个事务读取到了另一个事务未提交的数据):

    时间轴(可以自行把时间的流逝当做一条时间线)事务A事务B
    T1开始事务开始事务
    T2修改任意一张表中的任一记录(例如修改account表中的zhangsan余额,将余额由1000修改为2000)
    T3查询zhangsan的余额,结果为2000
    T4提交事务

    以上事务A读取到了事务B还未提交的数据,就会产生脏读

    不可重复读(一个事务中,两次读取到的数据不一致):

    时间轴(可以自行把时间的流逝当做一条时间线)事务A事务B
    T1开始事务开始事务
    T2查询zhangsan的余额,结果为1000
    T3修改任意一张表中的任一记录(例如修改account表中的zhangsan余额,将余额由1000修改为2000)
    T4提交事务
    T5查询zhangsan的余额,结果为2000

    我们可以很明显的看出来脏读与不可重复读的区别:脏读是一个事务读取另一个事务未提交的数据,然而不可重复读是一个事务读取另一个事务已经提交的数据。

    幻读(在一个事务中,按照某个条件先后查询两次数据库,两次查询的结果条数不同):

    时间轴(可以自行把时间的流逝当做一条时间线)事务A事务B
    T1开始事务开始事务
    T2查询0 < id < 5 的所有用户的余额。
    假设当前查出来的用户只有一条
    T3账户余额表中插入一条新的用户
    T4提交事务
    T5查询0 < id < 5 的所有用户的余额。
    现在查出来的当前用户有两条

    由此我们可以通俗的理解:

    不可重复读是数据变了,然而幻读是数据的行数变了。

    Mysql中解决脏读、幻读、不可重复读,使用的技术是MVCC(Multi-Version Concurrency Control)—多版本并发控制(mysql事务的隔离级别为 REPEATABLE-READ,可重复读

    首先说明MVCC中的数据结构(或者说MVCC是由什么组成的)

    1)隐藏列:InnoDB存储引擎中每行数据列都有隐藏列,隐藏列中包含了本行数据的事务id、指向undo log的指针等。

    补充一个知识点:undo log(撤销或回滚日志)可以这样理解:

    它记录了事务发生前的数据状态,但是除了SELECT之外。如果修改数据的时候发生异常,可以使用undo log 来实现回滚操作。

    但是它仅仅是逻辑日志,仅仅是将数据从逻辑上恢复事务之前的状态,而不是从物理上实现的,如果想从物理上实现回滚操作,还需要redo log来配合

    redo log现在暂时用不上,后面如果涉及到它的作用之后我们在说。

    2)基于undo log的版本链:每条undo log也会指向更早的undo log,这样就形成了一条数据的版本链。

    3)read-view:一致性视图,可以理解当第一条SELECT进来的时候,InnoDB存储引擎会在内存中生成一个当前所有事务的状态的快照(记住是所有),由于我们mysql是REPEATABLE-READ,那么这个视图会一直沿用下去,ORACLE数据库使用的是READ-COMMITTED(读已提交),ORACLE每次SELECT的时候都会生成一个read-view。

    上图中每一个数据行的后面,没有任何填充的格子我们可以叫他undo log,上面说过undo log的指向,由此就构成了一条数据的版本链。

    我们先看一些操作:

    温馨提示:如果看不清的话可以点击这里

    最左边的,有序增长的数据,我们依旧可以当做是一条时间线。

    当执行查询sql时会生成一致性视图read-view,它由执行查询时所有未提交事务id数组(数组里面最小的id为 min_id )和已经创建的最大事务id(max_id)组成,查询的数据结果需要跟read-view做比对从而得到快照结果。

    然后我们在来看一下MVCC的规则:

    版本链比对规则:
    1、如果落在绿色部分(trx_id < min_id),表示这个版本是已提交的事务生成的,那么这个数据是可见的;
    2、如果落在红色的部分(trx_id > max_id),表示这个版本是由将来启动的事务生成的,是肯定不可见的;
    3、如果落在黄色部分(min_id <= trx_id <= max_id),那就包含两种情况
         a.若 row的trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见,当前自己的事务是可见的
         b.若row的trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。
    
    对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录,如果delete flag标记为true,意味着记录已经被删除,则不返回数据。
    

    得知这些之后,我们来看一下在序号7这个时间点上进行了SELECT操作之后,会有什么样的效果:

    1、首先会生成一个一致性的视图read-view,当前的read-view中:

    2、未提交的事务数组中:最小的事务id是100,最大的未提交事务id是200,然后最大的事务id是300(已经提交)

    ps:上方图片的右上角

    3、这时我们看以序号7为时间点上的undo log版本链:

    4、此时我们可以在上述规则中进行对比,发现它落在了黄色部分,但是事务id为300 的事务不在数组中(注意数组是未提交事务id形成的数组,不要记混

    结论:所以此时查到的结果就显而易见:name=lilei300

    然后我们再继续往下走,来到序号为10的时间点上

    我们之前已经说过,mysql的隔离级别是REPEATABLE-READ,那么这个视图会一直沿用下去(思考未提交事务数组和最大事务id现在是多少?

    此时我们看一下当前时间点上的undo log的版本链:

    之后再按照上面的规则进行比较,发现现在仍然是落在了黄色部分,但是该行的事务id为100,在未提交的事务数组中,所以对本次SELECT操作时不可见的,就会继续顺着版本链向上找,直到找打了事务id为300这行记录。

    所以当前结果很显然:name=lilei300

    所以当前是不是就解决了我们说的脏读的问题?

    那么假如我们在时间点序号为13处添加一条sql语句

    select name from account where id = 1;
    

    思考一下它现在的undo log版本链,以及当前的read-view。

    现在我们来进行分析:

    同样,如果看不清可以点击这里

    我们使用的read-view仍然是第一次SELECT的时候使用的read-view(原因上面已经说过,不在赘述)。

    此时undo log版本链:

    现在的未提交事务数组:[100,200]

    最大事务id:300

    再与上面的规则进行比对:发现仍然是落在了黄色的部分,但是当前事务id在未提交数组中,此次查询是不可见的,然后按着顺序向上找,找到了事务id为100的记录,但是当前事务id仍然在未提交的数组中,所以此次查询仍然是不可见的。接着找到了事务id为300的那一行数据。

    结果:name=lilei300。

    思考:此时是否解决了不可重复读的问题?

    答案:是的。

    最后:大家可以按照此方法来试一下,MVCC解决幻读的过程。
    本文参考:

    ​ 1、https://www.cnblogs.com/kismetv/p/10331633.html

    ​ 2、https://www.bilibili.com/video/BV1YJ411J7vb?t=1378

    如有不对,欢迎指正。

    展开全文
  • #H2数据库多版本并发控制(MVCC)测试 在 1.4.x 中默认激活 MVCC 模式在 1.4.x 版本中默认启用,使用默认的 MVStore 存储引擎。 MVCC 在使用 PageStore 存储引擎时默认是禁用的(这是 1.3.x 版中的默认设置)。 使用...
  • InnoDB多版本并发控制机制MVCC的原理与实现,包括底层代码实现以及快照的构建原理。RR隔离级别和RC隔离级别的实现逻辑。
  • InnoDB中的MVCC

    2020-12-14 09:31:48
    什么是MVCC MVCC即Multi-Version Concurrency Control,译为多版本并发控制。 MVCC的背景 MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制...
  • mvcc-锈 在 Rust 中试验 MVCC
  • Postgres通过一个叫做 多版本并发控制(MVCC) 的机制做到了这一点。这个技术并不是Postgres所特有的:还有好几种数据库都实现了不同形式的MVCC,包括 Oracle、Berkeley DB、CouchDB 等等 。当你使用PostgreSQL来设计...
  • 53 理解MVCC机制的前奏:undo log版本链是个什么东西?l.pdf
  • 浅析MySQL – MVCC

    2021-01-21 14:27:59
    版本链 在InnoDB引擎表中,他们的聚簇索引记录中有两个隐藏列: trx_id:用来存储对数据进行修改时的事务id roll_pointer:每次对哪条聚簇索引记录有修改的时候,就会把老版本写入undo日志中。...
  • 本篇文章给大家详细介绍了一下关于Mysql隔离级别、锁与MVCC的相关知识,有这方面兴趣的朋友参考下。
  • mvcc

    2017-07-12 21:46:30
    MVCC简介 MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只...

    MVCC简介

    MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。

    MVCC实现原理

    innodb MVCC主要是为Repeatable-Read事务隔离级别做的。在此隔离级别下,A、B客户端所示的数据相互隔离,互相更新不可见

    了解innodb的行结构、Read-View的结构对于理解innodb mvcc的实现由重要意义

    innodb存储的最基本row中包含一些额外的存储信息 DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE BIT

    • 6字节的DATA_TRX_ID 标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1

    • 7字节的DATA_ROLL_PTR 指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针

    • 6字节的DB_ROW_ID,当由innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中不包括这个值.,这个用于索引当中
    • DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候

    具体的执行过程

    begin->用排他锁锁定该行->记录redo log->记录undo log->修改当前行的值,写事务编号,回滚指针指向undo log中的修改前的行

    上述过程确切地说是描述了UPDATE的事务过程,其实undo log分insert和update undo log,因为insert时,原始的数据并不存在,所以回滚时把insert undo log丢弃即可,而update undo log则必须遵守上述过程

    下面分别以select、delete、 insert、 update语句来说明

    SELECT

    Innodb检查每行数据,确保他们符合两个标准:

    1、InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行

    2、行的删除操作的版本一定是未定义的或者大于当前事务的版本号,确定了当前事务开始之前,行没有被删除

    符合了以上两点则返回查询结果。

    INSERT

    InnoDB为每个新增行记录当前系统版本号作为创建ID。

    DELETE

    InnoDB为每个删除行的记录当前系统版本号作为行的删除ID。

    UPDATE

    InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本。

    说明

    insert操作时 “创建时间”=DB_ROW_ID,这时,“删除时间 ”是未定义的;

    update时,复制新增行的“创建时间”=DB_ROW_ID,删除时间未定义,旧数据行“创建时间”不变,删除时间=该事务的DB_ROW_ID;

    delete操作,相应数据行的“创建时间”不变,删除时间=该事务的DB_ROW_ID;

    select操作对两者都不修改,只读相应的数据

    对于MVCC的总结

    上述更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,这个可能与我们所理解的MVCC有较大的出入,一般我们认为MVCC有下面几个特点:
    • 每行数据都存在一个版本,每次数据更新时都更新该版本
    • 修改时Copy出当前版本随意修改,各个事务之间无干扰
    • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)
    就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道,而Innodb的实现方式是:
    • 事务以排他锁的形式修改原始数据
    • 把修改前的数据存放于undo log,通过回滚指针与主数据关联
    • 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)
    二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC? 
     
    Innodb的实现真算不上MVCC,因为并没有实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。
     
    比如,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致Transaction2违反ACID。
     
    理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,而二提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。


    展开全文
  • 【MySQL笔记】正确的理解MySQL的MVCC及实现原理

    万次阅读 多人点赞 2019-07-05 15:43:06
    MVCC多版本并发控制 前提概要 MVCC实现原理 MVCC相关问题 前提概要 什么是MVCC? MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,...

    MVCC多版本并发控制


    如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!博客目录 | 先点这里

    !首先声明,MySQL 的测试环境是 5.7

    • 前提概要
      • 什么是 MVCC
      • 什么是当前读和快照读?
      • 当前读,快照读和 MVCC 的关系
    • MVCC 实现原理
      • 隐式字段
      • undo日志
      • Read View
      • 整体流程
    • MVCC 相关问题
      • RR 是如何在 RC 级的基础上解决不可重复读的?
      • RC, RR 级别下的 InnoDB 快照读有什么不同?

    前提概要


    什么是 MVCC ?

    MVCC
    MVCC,全称 Multi-Version Concurrency Control ,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
    mvcc - @百度百科

    MVCCMySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读


    什么是当前读和快照读?

    在学习 MVCC 多版本并发控制之前,我们必须先了解一下,什么是 MySQL InnoDB 下的当前读快照读?

    • 当前读
      像 select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

    • 快照读
      不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

    说白了 MVCC 就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现


    当前读,快照读和MVCC的关系

    • MVCC 多版本并发控制是 「维持一个数据的多个版本,使得读写操作没有冲突」 的概念,只是一个抽象概念,并非实现
    • 因为 MVCC 只是一个抽象概念,要实现这么一个概念,MySQL 就需要提供具体的功能去实现它,「快照读就是 MySQL 实现 MVCC 理想模型的其中一个非阻塞读功能」。而相对而言,当前读就是悲观锁的具体功能实现
    • 要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC 模型在 MySQL 中的具体实现则是由 3 个隐式字段undo 日志Read View 等去完成的,具体可以看下面的 MVCC 实现原理

    MVCC 能解决什么问题,好处是?

    数据库并发场景有三种,分别为:

    • 读-读:不存在任何问题,也不需要并发控制
    • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
    • 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

    MVCC 带来的好处是?
    多版本并发控制(MVCC)是一种用来解决读-写冲突无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题

    • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
    • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

    小结一下咯
    简而言之,MVCC 就是因为大佬们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了 MVCC,所以我们可以形成两个组合:

    • MVCC + 悲观锁
      MVCC解决读写冲突,悲观锁解决写写冲突
    • MVCC + 乐观锁
      MVCC 解决读写冲突,乐观锁解决写写冲突

    这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题


    MVCC 的实现原理


    MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段undo日志Read View 来实现的。所以我们先来看看这个三个 point 的概念

    隐式字段

    每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID 等字段

    • DB_TRX_ID
      6 byte,最近修改(修改/插入)事务 ID:记录创建这条记录/最后一次修改该记录的事务 ID
    • DB_ROLL_PTR
      7 byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里)
    • DB_ROW_ID
      6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
    • 实际还有一个删除 flag 隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除 flag 变了

    在这里插入图片描述
    如上图,DB_ROW_ID 是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID 是当前操作该记录的事务 ID ,而 DB_ROLL_PTR 是一个回滚指针,用于配合 undo日志,指向上一个旧版本


    undo日志

    undo log 主要分为两种:

    • insert undo log
      代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
    • update undo log
      事务在进行 updatedelete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除

    purge

    • 从前面的分析可以看出,为了实现 InnoDB 的 MVCC 机制,更新或者删除操作都只是设置一下老记录的 deleted_bit ,并不真正将过时的记录删除。

    • 为了节省磁盘空间,InnoDB 有专门的 purge 线程来清理 deleted_bit 为 true 的记录。为了不影响 MVCC 的正常工作,purge 线程自己也维护了一个read view(这个 read view 相当于系统中最老活跃事务的 read view );如果某个记录的 deleted_bit 为 true ,并且 DB_TRX_ID 相对于 purge 线程的 read view 可见,那么这条记录一定是可以被安全清除的。

    对 MVCC 有帮助的实质是 update undo logundo log 实际上就是存在 rollback segment 中旧记录链,它的执行流程如下:

    一、 比如一个有个事务插入 persion 表插入了一条新记录,记录如下,name 为 Jerry , age 为 24 岁,隐式主键是 1,事务 ID回滚指针,我们假设为 NULL

    二、 现在来了一个事务 1对该记录的 name 做出了修改,改为 Tom

    • 事务 1修改该行(记录)数据时,数据库会先对该行加排他锁
    • 然后把该行数据拷贝到 undo log 中,作为旧记录,既在 undo log 中有当前行的拷贝副本
    • 拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务 ID 为当前事务 1的 ID, 我们默认从 1 开始,之后递增,回滚指针指向拷贝到 undo log 的副本记录,既表示我的上一个版本就是它
    • 事务提交后,释放锁

    三、 又来了个事务 2修改person 表的同一个记录,将age修改为 30 岁

    • 事务2修改该行数据时,数据库也先为该行加锁
    • 然后把该行数据拷贝到 undo log 中,作为旧记录,发现该行记录已经有 undo log 了,那么最新的旧数据作为链表的表头,插在该行记录的 undo log 最前面
    • 修改该行 age 为 30 岁,并且修改隐藏字段的事务 ID 为当前事务 2的 ID, 那就是 2 ,回滚指针指向刚刚拷贝到 undo log 的副本记录
    • 事务提交,释放锁

    从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log 的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该 undo log 的节点可能是会 purge 线程清除掉,向图中的第一条 insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里


    Read View 读视图

    什么是 Read View?

    什么是 Read View,说白了 Read View 就是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID (当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大)

    所以我们知道 Read View 主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

    Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事务 ID )取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护),如果 DB_TRX_ID 跟 Read View 的属性做了某些比较,不符合可见性,那就通过 DB_ROLL_PTR 回滚指针去取出 Undo Log 中的 DB_TRX_ID 再比较,即遍历链表的 DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的 DB_TRX_ID , 那么这个 DB_TRX_ID 所在的旧记录就是当前事务能看见的最新老版本

    那么这个判断条件是什么呢?
    在这里插入图片描述
    我们这里盗窃@呵呵一笑百媚生一张源码图,如上,它是一段 MySQL 判断可见性的一段源码,即 changes_visible 方法(不完全哈,但能看出大致逻辑),该方法展示了我们拿 DB_TRX_ID 去跟 Read View 某些属性进行怎么样的比较

    在展示之前,我先简化一下 Read View,我们可以把 Read View 简单的理解成有三个全局属性

    • trx_list(名称我随意取的)
      • 一个数值列表
      • 用于维护 Read View 生成时刻系统 正活跃的事务 ID 列表
    • up_limit_id
      • lower water remark
      • 是 trx_list 列表中事务 ID 最小的 ID
    • low_limit_id
      • hight water mark
      • ReadView 生成时刻系统尚未分配的下一个事务 ID ,也就是 目前已出现过的事务 ID 的最大值 + 1
      • 为什么是 low_limit ? 因为它也是系统此刻可分配的事务 ID 的最小值
    • 首先比较 DB_TRX_ID < up_limit_id , 如果小于,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
    • 接下来判断 DB_TRX_ID >= low_limit_id , 如果大于等于则代表 DB_TRX_ID 所在的记录在 Read View 生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
    • 判断 DB_TRX_ID 是否在活跃事务之中,trx_list.contains (DB_TRX_ID),如果在,则代表我 Read View 生成时刻,你这个事务还在活跃,还没有 Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在 Read View 生成之前就已经 Commit 了,你修改的结果,我当前事务是能看见的

    整体流程

    我们在了解了 隐式字段undo log, 以及 Read View 的概念之后,就可以来看看 MVCC 实现的整体流程是怎么样了

    整体的流程是怎么样的呢?我们可以模拟一下

    • 事务 2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务 ID 为 2,此时还有事务1事务3在活跃中,事务 4事务 2快照读前一刻提交更新了,所以 Read View 记录了系统当前活跃事务 1,3 的 ID,维护在一个列表上,假设我们称为trx_list
    事务 1事务 2事务 3事务 4
    事务开始事务开始事务开始事务开始
    修改且已提交
    进行中快照读进行中
    • Read View 不仅仅会通过一个列表 trx_list 来维护事务 2执行快照读那刻系统正活跃的事务 ID 列表,还会有两个属性 up_limit_idtrx_list 列表中事务 ID 最小的 ID ),low_limit_id ( 快照读时刻系统尚未分配的下一个事务 ID ,也就是目前已出现过的事务ID的最大值 + 1 资料传送门 | 呵呵一笑百媚生的回答 ) 。所以在这里例子中 up_limit_id 就是1,low_limit_id 就是 4 + 1 = 5,trx_list 集合的值是 1, 3,Read View 如下图
    • 我们的例子中,只有事务 4 修改过该行记录,并在事务 2 执行快照读前,就提交了事务,所以当前该行当前数据的 undo log 如下图所示;我们的事务 2 在快照读该行记录的时候,就会拿该行记录的 DB_TRX_ID 去跟 up_limit_id , low_limit_id活跃事务 ID 列表( trx_list )进行比较,判断当前事务 2能看到该记录的版本是哪个。
    • 所以先拿该记录 DB_TRX_ID 字段记录的事务 ID 4 去跟 Read Viewup_limit_id 比较,看 4 是否小于 up_limit_id( 1 ),所以不符合条件,继续判断 4 是否大于等于 low_limit_id( 5 ),也不符合条件,最后判断 4 是否处于 trx_list 中的活跃事务, 最后发现事务 ID 为 4 的事务不在当前活跃事务列表中, 符合可见性条件,所以事务 4修改后提交的最新结果对事务 2 快照读时是可见的,所以事务 2 能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

    在这里插入图片描述

    • 也正是 Read View 生成时机的不同,从而造成 RC , RR 级别下快照读的结果的不同

    MVCC 相关问题


    RR 是如何在 RC 级的基础上解决不可重复读的?

    当前读和快照读在 RR 级别下的区别:

    表1:

    事务A事务B
    开启事务开启事务
    快照读(无影响)查询金额为500快照读查询金额为500
    更新金额为400
    提交事务
    select 快照读金额为500
    select lock in share mode当前读金额为400

    在上表的顺序下,事务 B 的在事务 A 提交修改后的快照读是旧版本数据,而当前读是实时新数据 400

    表2:

    事务A事务B
    开启事务开启事务
    快照读(无影响)查询金额为500
    更新金额为400
    提交事务
    select 快照读金额为400
    select lock in share mode当前读金额为400

    而在表 2这里的顺序中,事务 B 在事务 A 提交后的快照读和当前读都是实时的新数据 400,这是为什么呢?

    • 这里与上表的唯一区别仅仅是表 1的事务 B 在事务 A 修改金额前快照读过一次金额数据,而表 2的事务B在事务A修改金额前没有进行过快照读。

    所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力

    我们这里测试的是更新,同时删除更新也是一样的,如果事务B的快照读是在事务A操作之后进行的,事务B的快照读也是能读取到最新的数据的


    RC , RR 级别下的 InnoDB 快照读有什么不同?

    正是 Read View 生成时机的不同,从而造成 RC , RR 级别下快照读的结果的不同

    • 在 RR 级别下的某个事务的对某条记录的第一次快照读会创建一个快照及 Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个 Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个 Read View,所以对之后的修改不可见;
    • 即 RR 级别下,快照读生成 Read View 时,Read View 会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
    • 而在 RC 级别下的,事务中,每次快照读都会新生成一个快照和 Read View , 这就是我们在 RC 级别下的事务中可以看到别的事务提交的更新的原因

    总之在 RC 隔离级别下,是每个快照读都会生成并获取最新的 Read View;而在 RR 隔离级别下,则是同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View。


    MySQL系列


    参考资料


    展开全文
  • MVCC 到底是什么?MVCC 即多版本控制器,其特点就是在同一时间,不同事务可以读取到不同版本的数据,从而去解决脏读和不可重复读的问题。这样的解释你看了不下几十遍了吧!但是你真的理解什么...

    MVCC 到底是什么?

    MVCC 即多版本控制器,其特点就是在同一时间,不同事务可以读取到不同版本的数据,从而去解决脏读和不可重复读的问题。

    这样的解释你看了不下几十遍了吧!但是你真的理解什么是多版本控制器吗?

    ①生活案例:搬家

    最近小 Q 跟自己的女朋友需要搬到新家,由于出小区的的时候需要支付当月的物业费。于是小 Q 跟自己的女朋友同时登陆了小区提供的物业缴费系统。

    ②悲观并发控制

    假设小 Q 正在查当月需要缴纳的费用是多少,然后进行支付的时候,此时小 Q 查询的这条数据是已经被锁定的。

    那么小 Q 女朋友是无法访问该数据的,直至小 Q 支付完成或者退出系统将悲观锁释放,小 Q 的女朋友才可以查询到数据。

    悲观锁保证在同一时间只能有一个线程访问,默认数据在访问的时候会产生冲突,然后在整个过程都加上了锁。

    这样的系统对于站在程序员的角度看就是毫无用户体验感,如果多个人需要同时访问一条信息,只能在一台设备上看喽!

    ③乐观并发控制

    在小 Q 查看物业费欠费情况,并且支付的同时,小 Q 的女朋友也可以访问到该数据。

    乐观锁认为即使在并发环境下,也不会产生冲突问题,所以不会去做加锁操作。而是在数据提交的时候进行检测,如果发现有冲突则返回冲突信息。

    小结:Innodb 的 MVCC 机制就是乐观锁的一种体现,读不加锁,读写不冲突,在不加锁的情况下能让多个事务进行并发读写,并且解决读写冲突问题,极大的提高系统的并发性。

    悲观锁、乐观锁

    锁按照粒度分为表锁、行锁、页锁;按照使用方式分为共享锁、排它锁;根据思想分为乐观锁、悲观锁。

    无论是乐观锁、悲观锁都只是一种思想而已,并不是实际的锁机制,这点一定要清楚。

    ①悲观锁(悲观并发控制)

    悲观锁实际为悲观并发控制,缩写 PCC。

    悲观锁持消极态度,认为每一次访问数据时,总是会发生冲突,因此,每次访问必须先锁住数据,完成访问后在释放锁。

    保证在同一时间只有单个线程可以访问,实现数据的排它性。同时悲观锁使用数据库自身的锁机制实现,可以解决读-写,写-写的冲突。

    那么在什么场景下可以使用悲观锁呢?悲观锁适用于在写多读少的并发环境下使用,虽然并发效率不高,但是保证了数据的安全性。

    ②乐观锁(乐观并发控制)

    跟悲观锁一样,乐观锁实际为乐观并发控制,缩写为OCC。

    乐观锁相对于悲观锁而言,认为即使在并发环境下,外界对数据的操作不会产生冲突,所以不会去加锁,而是会在提交更新的时候才会正式的对数据的冲突与否进行检测。

    如果发现冲突,要么再重试一次,要么切换为悲观的策略。乐观并发控制要解决的是数据库并发场景下的写-写冲突,指用无锁的方式去解决。

    MVCC 解决了哪些问题

    在事务并发的情况下会产生以下问题:

    • 脏读:读取其他事务未提交的数据。

    • 不可重复读:一个事务在读取一条数据时,由于另一个事务修改了这条数据,导致前后读取的数据不一致。

    • 幻读:一个事务先读取了某个范围的数量,同时另一个事务新增了这个范围的数据,再次读取发现俩次得到的结果不一致。

    MVCC 在 Innodb 存储引擎的实现主要是为了提高数据库并发能力,用更好的方式去处理读–写冲突,同时做到不加锁、非阻塞并发读写。

    但是 MVCC 可以解决脏读,不可重复读,MVCC 使用快照读解决了部分幻读问题,但是在修改时还是使用的当前读,所以还是存在幻读问题,幻读的问题最终就是使用间隙锁解决。

    当前读、快照读

    在了解 MVCC 是如何解决事务并发带来的问题之前,需要先明白俩个概念,当前读、快照读。

    ①当前读

    给读操作加上共享锁、排它锁,DML 操作加上排它锁,这些操作就是当前读。

    共享锁、排它锁也被称之为读锁、写锁。共享锁与共享锁是共存的,但是如果要修改、添加、删除时,必须等到共享锁释放才可进行操作。

    因为在 Innodb 存储引擎中,DML 操作都会隐式添加排它锁。所以说当前读所读取的记录就是最新的记录,读取数据时加上锁,保证其他事务不能修改当前记录。

    ②快照读

    如果你看到这里就默认你对隔离级别有一定的了解哈!快照读的前提是隔离级别不是串行级别,串行级别的快照读会退化成当前读。

    快照读的出现旨在提高事务并发性,其实现基于本文的主角 MVCC 即多版本控制器。

    MVCC 可以认为就是行锁的一个变种,但是它在很多情况下避免了加锁操作。所以说快照读读取的数据有可能不是最新的,而是之前版本的数据。

    为什么要提到快照读呢!因为在 read-view 就是通过快照读生成的,为了防止后文概念模糊,所以在这里进行说明。

    ③如何区分当前读、快照读

    不加锁的简单的 select 都属于快照读:

    select id name user where id = 1;
    

    与之对应的则是当前读,给 select 加上共享锁、排它锁:

    select id name from user where id = 1 lock in share mode;
    
    select id name from user where id = 1 for update;
    

    MVCC 实现三大要素

    终于来到本文最终要的部分了,前边的叙述都是为了原理这一块做的铺垫。

    在这之前需要知道 MVCC 只在 REPEATABLE READ(可重复读) 和  READ COMMITTED(已读提交)这俩种隔离级别下适用。

    MVCC 实现原理是由俩个隐式字段、undo 日志、Read view 来实现的。

    ①隐式字段

    在 Innodb 存储引擎中,在有聚簇索引的情况下每一行记录中都会隐藏俩个字段,如果没有聚簇索引则还有一个 6byte 的隐藏主键。

    这俩个隐藏列一个记录的是何时被创建的,一个记录的是什么时候被删除。这里不要理解为是记录的是时间,存储的是事务 ID。

    俩个隐式字段为 DB_TRX_ID,DB_ROLL_PTR,没有聚簇索引还会有 DB_ROW_ID 这个字段:

    • DB_TRX_ID:记录创建这条记录上次修改他的事务 ID。

    • DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本。

    隐式字段实际还有一个 delete flag 隐藏字段,即记录被更新或删除,这里的删除并不代表真的删除,而是将这条记录的 delete flag 改为 true(这里埋下一个伏笔,数据库的删除是真的删除吗?)

    ②undo log(回滚日志)

    之前对 undo log 的作用只提到了回滚操作实现原子性,现在需要知道的另一个作用就是实现 MVCC 多版本控制器。

    undo log 细分为俩种:

    • insert 时产生的 undo log、update

    • delete 时产生的 undo log

    在 Innodb 中 insert 产生的 undo log 在提交事务之后就会被删除,因为新插入的数据没有历史版本,所以无需维护 undo log。

    update 和 delete 操作产生的 undo log 都属于一种类型,在事务回滚时需要,而且在快照读时也需要,则需要维护多个版本信息。

    只有在快照读和事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一删除。

    purge 线程会清理 undo log 的历史版本,同样也会清理 del flag 标记的记录。

    写到这里关于 undo log 在 MVCC 中的作用估计还是蒙圈的。

    undo log 在 MVCC 中的作用:undo log 保存的是一个版本链,也就是使用 DB_ROLL_PTR 这个字段来连接的。当数据库执行一个 select 语句时会产生一致性视图 read view。

    那么这个 read view 是由在查询时所有未提交事务 ID 组成的数组,数组中最小的事务 ID 为 min_id 和已创建的最大事务 ID 为 max_id 组成,查询的数据结果需要跟 read-view 做比较从而得到快照结果。

    所以说 undo log 在 MVCC 中的作用就是为了根据存储的事务 ID 做比较,从而得到快照结果。

    ③undo log 底层实现

    假设一开始的数据为下图:

    此时执行了一条更新的 SQL 语句:

    update user set name = 'niuniu where id = 1'
    

    那么 undo log 的记录就会发生变化,也就是说当执行一条更新语句时会把之前的原有数据拷贝到 undo log 日志中。

    同时你可以看见最新的一条记录在末尾处连接了一条线,也就是说 DB_ROLL_PTR 记录的就是存放在 undo log 日志的指针地址。

    最终有可能需要通过指针来找到历史数据:

    ④read-view

    当执行 SQL 语句查询时会产生一致性视图,也就是 read-view,它是由查询的那一时间所有未提交事务 ID 组成的数组,和已经创建的最大事务 ID 组成的。

    在这个数组中最小的事务 ID 被称之为 min_id,最大事务 ID 被称之为 max_id,查询的数据结果要根据 read-view 做对比从而得到快照结果。

    于是就产生了以下的对比规则,这个规则就是使用当前的记录的 trx_id 跟 read-view 进行对比,对比规则如下。

    ⑤版本链对比规则

    如果落在 trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交所以数据是可见的。

    如果落在 trx_id>max_id,表示此版本是由将来启动的事务生成的,是肯定不可见的。

    如果落在 min_id<=trx_id<=max_id 会存在俩种情况

    • 如果 row 的 trx_id 在数组中,表示此版本是由还没提交的事务生成的,不可见,但是当前自己的事务是可见的。

    • 如果 row 的 trx_id 不在数组中,表明是提交的事务生成了该版本,可见。

    在这里还有一个特殊情况那就是对于已经删除的数据,在之前的 undo log 日志讲述时说了 update 和 delete 是同一种类型的 undo log,同样也可以认为 delete 就是 update 的特殊情况。

    当删除一条数据时会将版本链上最新的数据复制一份,然后将 trx_id 修改为删除时的 trx_id,同时在该记录的头信息中存在一个 delete flag 标记,将这个标记写上 true,用来表示当前记录已经删除。

    在查询时按照版本链的规则查到对应的记录,如果 delete flag 标记位为 true,意味着数据已经被删除,则不返回数据。

    如果你对这里的 read-view 的生成和版本链对比规则不懂,不要着急,也不要在这里浪费时间,请继续往下看,我会使用一个简单的案例和一个复杂的案例给大家重现上述的规则。

    MVCC 底层原理

    案例一

    下图是准备的素材,这里应该都理解 select 返回的结果为 niuniu,即事务 102 修改后的结果:

    在上图中可以看到有三个事务在进行。事务 ID 为 100、101 是修改的其他表,只有事务 ID 为 102 修改的需要查询的这张表。

    接下来看看 select 这一列查询返回的结果是不是就是事务 ID 为 102 修改的结果。

    此时生成的 read-view 为 [100,101],102,那么现在就可以返回去看一下 read-view 规则,在这里事务 ID100 就是 min_id,事务 ID102 就是 max_id。

    这个 select 语句返回结果肯定是 niuniu。那么接下来看一下在 MVCC 中是如何查找数据的。

    当前版本链:

    那么就会拿着这个 trx_id 为 102 进行比对,会发现这个 102 就是 max_id,然后你再看一下版本链的对比规则中第三种情况。

    如果落在 min_id<=trx_id<=max_id 会存在俩种情况。此时信息就已经非常明确了,事务 ID102 是没有在数组中的,所以表示这个版本是已经提交的事务生成的,那么就是可见的呗!

    毫无疑问查询会返回 niuniu 这个值,先通过这个简单的案例让你对版本链有一个简单的理解,接下来将使用一个比较繁琐的案例再来跟大家演示一遍。

    案例二

    本例要求知道 select 的第二次查询结果。深黑色字体。同样是在 kaka 那一条记录的基础上。

    当事务 ID100 俩次更新后,版本链也会改变,此时的版本链如下图。红色部分为最新数据,蓝色数据为 undo log 的版本链数据。

    对于此时生成的 read-view 你会有什么疑问,在 RR 级别也就是可重复读的隔离级别下。

    当在一个事务下执行查询时,所有的 read-view 都是沿用的第一条查询语句生成的。

    那此时的 read-view 也就是[100,101],102。

    看一下底层查找步骤:

    • 当前数据的事务 ID 为 100

    • 根据规则会落在 min_id<=trx_id<=max_id 这个区间

    • 并且当前行的事务 ID100 是在 read-view 的数组中的,表示此时的事务还没有提交则不可见

    • 继续在版本链中往下寻找,此时找到的事务 ID 还是 100,跟上述流程一致

    • 通过查找版本链,将发现事务 ID 为 102

    • 102 是 read-view 的 max_id,同样也会落在 min_id<=trx_id<=max_id 这个区间,但是跟之前不同的是,事务 102 是没有在数组中的,表示这个版本事务已经提交了所以是可见的

    • 最后返回的是 niuniu

    案例三

    为了让大家体会一下可重复读级别生成的 read-view 是根据在同一事务中第一条快照读产生的,再来看一个案例。

    此时的事务 ID101 也再对数据更新两次,然后在进行查询看一下会返回什么值:

    经过案例一、案例二的熟悉,现在对 undo log 的版本链和对比规则已经有了一定的了解了吧!

    案例三就不在那么详细的说明了,此时的版本链如下:

    此时的 read-view 依然为[100,101],102。

    那么首先会根据事务 101 去版本链对比,事务 101 和事务 100 都会落在 min_id<=trx_id<=max_id 这个区间,并且还都在数组中,所以数据是不可见的。

    那么继续往版本链中寻找就会到事务 102,这个是最大的事务 ID 并且不在数组中,所以是可见的。

    于是最终的返回结果还是 niuniu。

    案例四

    可以看到个案例三的图不同的是新增了一个查询语句,那么假设这两条语句执行的时间都是一致的,它们返回的结果会相同吗?

    案例三查询到的值为 niuniu:

    其实现在版本链跟案例三也是一致的:

    那么来理一下寻找过程:

    • 首先这里的 read-view 发生了变化,此时的 read-view 为[101],102。

    • 拿着当前的事务 ID101 跟版本链规则进行对比,落盘在 min_id<=trx_id<=max_id,并且在数组中,则数据不可见。

    • 然后进入版本链,找到下一个数据的事务 ID,还是 101,与上一个一致。

    • 接下来是事务 ID100。

    • 事务 ID100 是落在 trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交,所以数据是可见的。

    • 所以最终返回结果为 niuniu2。

    小结:在同一个事务中进行查询,会沿用第一次查询语句生成的 read-view(前提是隔离级别是在可重复读)。

    通过以上的四个案例,在版本链寻找过程中,可以总结出一个小技巧:

    根据这个小技巧你可以很快的得知此版本是否可见:

    • 如果当前的事务 ID 在绿色部分,是已经提交事务,则数据可见。

    • 如果当前的事务 ID 在蓝色部分,会有两种情况,如果当前事务 ID 在 read-view 数组内,是没有提交的事务不可见,如果不在数组内数据可见。

    • 如果落在红色部分,则不考虑,对于未来的事情不去想即可。

    总结

    阅读本文后,在面试过程中极大可能会遇到的问题就是聊聊你对 MVCC 的认识。

    本文内容从浅到深,从什么是 MVCC 到 MVCC 的底层实现,一步一步地陈述了 MVCC 的实现原理。

    本文简单总结:

    • MVCC 在不加锁的情况下解决了脏读、不可重复读和快照读下的幻读问题,一定不要认为幻读完全是 MVCC 解决的。

    • 对当前读、快照读理解,简单点说加锁就是当前读,不加锁的就是快照读。

    • MVCC 实现的三大要素:两个隐式字段、回滚日志、read-view。

    • 两个隐式字段:DB_TRX_ID:记录创建这条记录最后一次修改该记录的事务ID;DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本。

    • undo log 在更新数据时会产生版本链,是 read-view 获取数据的前提。

    • read-view 当 SQL 执行查询语句时产生的,是由为提交的事务 ID 组成的数组和创建的最大事务 ID 组成的。

    • 版本链规则看第六节的小结即可。

    作者:咔咔

    简介:坚持学习、坚持写博、坚持分享是咔咔从业以来一直所秉持的信念。希望在偌大互联网中咔咔的文章能带给你一丝丝帮助。我是咔咔,下期见。

    编辑:陶家龙

    展开全文
  • mvcc原理详解

    千次阅读 2020-11-21 20:46:32
    前提:在介绍mvcc之前,先简单介绍一下mysql事务的相关问题,mvcc归根结底是用来解决事务并发问题的,当然这个解决不是全部解决,只是解决了其中的一部分问题! mysql事务 一、事务的基本要素(ACID)  1、原子性...
  • 最近项目中遇到了一个分布式系统的并发控制问题。该问题可以抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2…Ln组成;...L的业务逻辑可以抽象为下面3个步骤:在没有事务支持的情况下,多个L进行并发处理...
  • 蒙哥MVCC 该项目在 MongoDB 之上实现了多版本并发控制范式 (MVCC)。 MVCC 允许无锁的一致和并发数据库访问。 有关如何使用此库的完整说明,请参阅 。 执照 该库已在 GNU 宽松通用公共许可证 (LGPL) v3.0 下发布。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,104
精华内容 13,641
关键字:

mvcc