暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MVCC 原理

原创 张正沛 2022-11-04
340

--参考MySQL实战45讲:08事务到底是隔离的还是不隔离的?

<<Undo Redo MVCC.pptx>>

一、MVCC简介

   1、启动事务  2、事务ID  3、row trx_id  4、回滚段(undo log)

二、MVCC 实现原理

   1、一致性视图   2、可见性判断(高低水位+活跃事务列表)

三、MVCC 数据处理

   1、MVCC 增删改查原理  2、数据查询逻辑   3、数据更新逻辑

四、MVCC 事务隔离

   1、InnoDB解决幻读   2、repeatable read 的实现  3、RR 和 RC 的区别

五、InnoDB MVCC 总结

解决读-写冲突的无锁并发控制,读写互不阻塞,避免了脏读和不可重复读。

一、MVCC简介

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

   数据的多版本(undo记录) 和 一致性视图(consistent read view) 实现了数据库的多版本并发控制(MVCC)。

1、启动事务

   begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。

   创建read-view的位置,是begin后面的SQL语句的位置,begin的时候不会分配事务id,只有执行了sql之后才会分配事务id。

   如果你想马上启动一个事务,产生readview,分配事务id,可使用命令:mysql> start transaction with consistent snapshot;

2、事务ID

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

   InnoDB 的 MVCC,在每行记录后面保存两个隐藏列:创建时间、删除时间,实际存储的时间是系统版本号。每开始一个新的事务,系统版本号都会递增。事务开始时刻的系统版本号,会作为当前事务的版本号,用来和查询到的每行记录的版本号进行比较。保存两个额外的系统版本号,使大部分读操作都可以不用加锁。

3、row trx_id

   每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。

DB_TRX_ID

DB_ROLL_PTR

DB_ROW_ID

VALUE

Innodb表在记录行上会多出三个字段:

  • 6字节的事务ID
  • 7字节的指向回滚段的指针
  • 6字节的ROW_ID

 

4、回滚段(undo log)

回滚日志删除:当系统里没有比这个回滚日志更早的 read-view 的时候。

计算机生成了可选文字:
亡k:恂
亡k:
亡陋1:17
.Et粼訃
抖k=Zi
《rx橛·.丬疒7.n

    V1、V2、V3 为一个记录的多个修改版本。但不是物理真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。      

    U1、U2、U3 表示 undo log。

    如果有一个事务,它的低水位(活跃事务列表最小值)是 18 ,row_trx_id=22 大于 18,那么当前记录对该事务不可见,就会从 V4 通过 U3 计算出 V3 的 row_trx_id=17 小于 低水位18 ,所以就读取 V3 的值。

 

二、MVCC 实现原理

   InnoDB 在实现 MVCC 时用到的一致性读视图(consistent read view),用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。

1、一致性视图

一致性视图(consistent read view):活跃事务列表 + 高水位

活跃事务列表:

   InnoDB 在事务启动瞬间,为每个事务构造了一个数组(活跃事务列表):用来保存这个当前正在“活跃”的所有事务ID。“活跃”指的就是,启动了但还没提交。

低水位:活跃事务列表数组里面事务 ID 的最小值记为低水位

高水位:当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。

2、可见性判断(高低水位+活跃事务列表)

    数据版本的可见性规则,就是基于数据的 row trx_id 和一致性视图的对比结果得到的。

计算机生成了可选文字:
当前
亻*位
乙来檠蕕拷

   数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到的。当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:

  • 如果row上的trx_id < 低水位,说明该记录已提交,这条记录对于当前事务可见
  • 如果row上的trx_id > 高水位, 说明该记录在当前事务之后提交,这条记录对于当前事务不可见,需要调用上一个可见的undo
  • 如果row上的低水位< trx_id < 高水位

  a.若row trx_id在数组(活跃事务列表)中,表示该记录还未提交,对当前事务不可见,需要调用上一个的undo,直到满足可见的条件。

  b.若row trx_id不在数组中(非活跃事务列表,已提交),表示这个版本是已经提交了的事务生成的,对于当前事务可见。

 

三、MVCC 数据处理

1、MVCC 增删改查原理

select

Innodb检查每行数据,符合以下两个标准,则返回该行数据。

  • InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行。
  • 删除行的版本要么未定义、要么大于当前事务的版本号,确保了事务读取到的行,在事务开始之前未被删除。

insert

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

delete

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

update

InnoDB将update转换成insert + delete。insert保存当前系统版本号为行版本号,同时保存当前系统版本号到原来的行作为行的删除标识。

2、数据查询逻辑

    以当前事务启动的时刻为准,如果一个数据版本是在当前事务启动之前生成的,就认;如果是当前事务启动以后才生成的,就不认,必须要找到它的上一个版本。如果“上一个版本”也不可见,那就得继续往前找。还有,如果是这个事务自己更新的数据,它自己还是要认的。

   对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

  • 版本未提交,不可见;
  • 版本已提交,但是是在视图创建后提交的,不可见;
  • 版本已提交,而且是在视图创建前提交的,可见。

3、数据更新逻辑

mysql> create table t (id int(11) not null,k int(11) default null,primary key (id)) engine=innodb;

mysql> insert into t(id, k) values(1,1),(2,2);

假设:事务 A 开始前,系统里面只有一个活跃事务 ID 是 99;事务 A、B、C 的版本号分别是 100、101、102,且当前系统里只有这四个事务;三个事务开始前,(1,1)这一行数据的 row trx_id 是 90。

read-view:事务 A 的视图数组就是 [99,100], 事务 B 的视图数组是 [99,100,101], 事务 C 的视图数组是 [99,100,101,102]。

Step

事务A

事务B

事务C

1

start transaction with consistent snapshot;

 

 

2

 

start transaction with consistent snapshot;

 

3

 

 

update t set k=k+1 where id=1;

4

 

update t set k=k+1 where id=1;

select k  from t where id=1;

 

事务C没有显式地使用 begin/commit

5

select k  from t where id=1;

commit;

 

表示这个 update 语句本身就是一个事务,语句完成的时候会自动提交。

6

 

commit;

 

当前读:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。

查询步骤:

   事务 C 先更新生成102版本的数据(1,2)。事务 B 在更新的时候,当前读拿到的是102版本的数据(1,2),更新后生成了101版本的数据(1,3)。所以,在事务 B 查询的时候,发现最新数据的版本号也是 101,是自己的更新,可以直接使用,查询得到的 k 的值是 3。

查询结果:

   事务 B 查到的 k 的值是 3,而事务 A 查到的 k 的值是 1

 

四、MVCC 事务隔离

1、InnoDB解决幻读问题

   多版本并发控制(MVCC,Multiversion Concurrency control)

2、repeatable read 的实现

    MySQL 默认的事务隔离级别 repeatable read,是通过MVCC实现的。

    可重复读的核心就是一致性读(consistent read);

    而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

3、RR(可重复读隔离级别)和 RC(读提交隔离级别)主要的区别

MVCC只在 repeatable reads 和 read committed 两个隔离级别下工作。

  • RR(可重复读级别):只在事务开始时创建read-view(执行sql之后),之后事务里的查询都共用该一致性视图,直到事务结束。
  • RC(读提交隔离级别):事务中每一个语句执行前都会重新算出一个新 read-view。

   如果是可重复读隔离级别,事务启动的时候会创建一个视图 read-view,之后事务执行期间,即使有其他事务修改了数据,事务看到的仍然跟在启动时看到的一样。

  • 对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
  • 对于读提交,查询只承认在语句启动前就已经提交完成的数据;

 

五、InnoDB MVCC 总结

    Innodb的实现真算不上MVCC,因为并没有实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。

    比如,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致Transaction2违反ACID。

    理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,而二提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。

MVCC有下面几个特点:

  • 每行数据都存在一个版本,每次数据更新时都更新该版本
  • 修改时Copy出当前版本随意修改,各个事务之间无干扰
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

Innodb 保存数据的方式:

  • 事务以排他锁的形式修改原始数据
  • 把修改前的数据存放于undo log,通过回滚指针与主数据关联
  • 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)

OCC 和 MVCC 的区别

  多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读

  乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,若没有就提交,如果有就放弃并重试。乐观并发控制类似自选锁。乐观并发控制适用于低数据争用,写冲突比较少的环境。

 

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论