由浅入深全面理解MVCC-1

目录

1、MVCC的理解

1.1 MVCC的概念

1.2 MVCC的注意事项

2、MySQL中的隐藏字段

3、读视图:ReadView

3.1 ReadView的概念

3.2 ReadView中的变量 trx_ids

3.3 ReadView中的变量 trx_ids 理解举例

4、回滚日志:Undo log

4.1 插入操作:insert

4.2 删除操作:delete

4.3 修改操作:update

4.4 查询操作:select

5、MVCC 的实现原理细节

6、当前读与快照读

6.1 当前读

6.2 快照读

99、参考

1、MVCC的理解

1.1 MVCC的概念

MVCC,Multi-Version Concurrency Control,多版本并发控制。

MVCC的含义是多版本并发控制,它是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。在数据库中的实现中,MVCC的目的就是为了解决读写冲突。

使用锁和锁协议来实现相应的 事务隔离级别 来进行并发控制 会因为锁而造成事务的阻塞。而MVCC 使得对同一行记录做读写的事务之间不用相互阻塞等待,由此提高了事务的并发能力,可以认为 MVCC是一种解决多个事务之间读写阻塞等待的行级锁。

MVCC的数据库表中,每一行数据都可能存在多个版本,对数据库的任何修改的提交commit 都不会直接覆盖之前的数据,而是产生一个新的版本与老版本共存,通过读写数据时读不同的版本来避免加锁阻塞。

1.2 MVCC的注意事项

  • (1)在MySQL的InnoDB引擎中,MVCC 只支持(已提交读 RC)和(可重复读 RR)的事务隔离级别。
  • (2)MVCC 能解决 脏读、不可重复读问题,不能解决幻读问题。
  • (3)MVCC 是用来解决 多个事务的读写操作之间的阻塞等待问题。

2、MySQL中的隐藏字段

在MySQL中,每行记录除了我们自定义的字段之外,还有数据库隐式定义的一些字段:DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID 等字段。

(1)DB_TRX_ID:数据行事务版本号,大小为6byte,记录最近修改事务ID(这里的修改包含:update / insert),记录创建这条记录 / 最后一次修改这条记录的事务ID。

(2)DB_ROLL_PTR:回滚版本号,大小为7byte,记录回滚指针,指向当前记录行的 undo log信息(指向该数据的前一个版本数据)。

(3)DB_ROW_ID:行记录隐式ID,大小为6byte,每行记录都存在着一个隐含的自增ID(隐藏自增主键,并不是我们在表中创建的自增主键ID)。如果我们在创建数据表时没有创建主键ID,那么,InnoDB存储引擎会自动的以 DB_ROW_ID 产生一个聚簇索引来作为自增主键ID。

3、读视图:ReadView

3.1 ReadView的概念

读视图,也就是 ReadView,其实就相当于一种快照 snapshot,里面记录了系统中 当前活跃事务ID列表 以及相关信息,主要用途是:用来判断数据的可见性,也即 判断当前事务是否有资格去访问该行数据。

3.2 ReadView中的变量 trx_ids

ReadView 中有多个变量,其中之一是 trx_ids,叫做 活跃事务ID列表,解释如下:

trx_ids:trx_ids 变量存储了当前处于活跃状态的事务ID列表,也就是 ReadView 开始创建时所有还未提交的活跃事务的事务ID列表。

【例如】事务A 在创建 ReadView(简单理解为 快照)时,数据库中事务B 和事务C 还没有提交或者回滚 以结束事务,此时,trx_ids 变量就会将事务B 和事务C 的事务ID 记录下来。

3.3 ReadView中的变量 trx_ids 理解举例

假设,当前事务生成了一个 ReadView,trx_ids 列表里的事务ID为 [ 60, 100 ]。

(1)如果你要访问的记录版本的事务ID为50,比 当前活跃事务ID列表 中最小的事务ID 60 还要小,那就说明这个事务 ( ID=50的事务 ) 在 ReadView 生成之前就已经被提交了,所以,事务ID=50 的记录版本 对当前活跃的事务来说 可见

(2)如果你要访问的记录版本的事务ID为70,通过比较发现 此事务ID 在当前活跃事务ID列表 的最小值 60 和 最大值 100 之间,那么,就需要再判断一下 70 这个事务ID 是否在 当前活跃事务ID列表 内。如果在,那就说明此事务 ( ID=70的事务 ) 还未提交,所以,事务ID=70 的记录版本对当前活跃的事务来说 不可见。如果不在,那就说明此事务 ( ID=70的事务 ) 已经提交,所以,事务ID=70 的记录版本 对当前活跃的事务来说 可见

(3)如果你要访问的记录版本的事务ID为110,通过比较发现 110 比当前活跃事务ID列表中的最大ID 100 都大,那就说明 这个版本 ( ID=110的事务 ) 是在 ReadView 生成之后才发生的,所以,事务ID=110 的记录版本对当前活跃的事务来说 不可见

4、回滚日志:Undo log

Undo log,也即是MySQL中的回滚日志,Undo log 中存储的是老版本数据,主要是用来 在事务中做回滚操作。当一个事务需要读取记录行时,如果当前记录行不可见,那么,可以通过回滚指针 DB_ROLL_PTR 顺着 undo log链 找到满足其可见性条件的记录行版本。

在InnoDB存储引擎中,undo log 主要分为如下两类:

(1)insert undo log : 事务对 insert 新记录时产生的 undo log, 只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。

(2)update undo log : 事务对已有记录进行 delete 或者 update 操作时产生的 undo log,在事务回滚时需要,不可丢弃。

实际上还有一个隐藏字段叫做 “删除flag”,它记录被更新或删除,但并不代表真的删除,而是 删除flag 变了。

4.1 插入操作:insert

插入操作:获取最新的事务版本号 n,保存 n 到对应的【数据行的版本号 DB_TRX_ID 中】,另外,删除版本号 DB_ROLL_PT 都是 null 。

4.2 删除操作:delete

删除操作:获取最新的事务版本号 n,保存 n 到对应的【删除版本号 DB_ROLL_PT 中】,另外,【数据行的版本号 DB_TRX_ID 】不变。

4.3 修改操作:update

修改操作:变为 insert 和 delete 操作的组合。先获取最新的事务版本号 n,然后,进行数据行拷贝(即 在版本链中把旧的数据行拷贝一份出来),在版本链中插入新拷贝的数据行,并且,保存 n 到 新插入的【数据行的版本号 DB_TRX_ID】中,与此同时,保存 n 到旧的数据行的【删除版本号 DB_ROLL_PT】中。

4.4 查询操作:select

查询操作:获取最新的事务版本号 n,查询行版本号 小于或者等于 n 的版本链中的行数据,防止读到其他事务还未提交的活跃数据。

注意:数据查询的规则详解如下:

(1)在版本链中,查找数据行版本 早于当前事务版本的数据行(也就是 行的系统版本号要小于或者等于当前事务的系统版本号)。这样就可以确保:当前事务读取的行,要么是在当前事务开始前已经存在了的,要么是当前事务自身插入或者修改过的行。

(2)在版本链中,查找删除版本号 要么是null、要么大于当前事务版本号的数据行这样就可以确保:当前事务读取的行,在事务开启之前没有被删除。

5、MVCC 的实现原理细节

参考:https://baijiahao.baidu.com/s?id=1629409989970483292&wfr=spider&for=pc

版本记录都是去版本链里面找的,然后,根据不同的隔离级别生成的 读视图 ReadView 就会有所不同。

例如:在一个 读已提交 RC 或者 可重复读 RR 的事务隔离级别中,有一个事务id=100的事务,修改了name,使得的 name 等于小明2,但是,该事务还没有提交。则,此时的版本链如下:

此时,另一个事务发起了select 语句 要查询 id=1 的记录,那么,此时生成的 ReadView 活跃事务ID列表就是 [100] 。最新版本100 是活跃事务,所以不能访问,那么,就得使用版本链去找了。

首先,找事务ID=100的下一个版本 name=小明1 的记录,发现 trx_id=60,小于活跃事务ID列表中的最小id,所以,可以访问,直接访问结果为 name=小明1。

那么,这个时候我们把之前 事务id=100的事务提交了,并且新建了一个 事务id=110 并且在该事务中也修改id=1的记录,name修改为小明3,并且不提交事务。则,此时的版本链如下:

这个时候,之前那个 select 事务 又执行了一次查询,要查询id=1的记录。这时候会发生两种情况:

(1)如果隔离级别是:已提交读 RC

会重新生成一个 ReadView,那么【select 事务】 的活跃事务ID列表中的值就变了,变成了 [110],然后再通过版本链查 trx_id 做对比,发现 查到的只能是 name=小明2。这个过程,其实就是 在本事务中 可以读取到其他事务已经提交的数据。

(2)如果隔离级别是:可重复读 RR
ReadView 还是在【select 事务】中第一次 select 的时候生成的那个 ReadView,也就是说 活跃事务ID列表的值还是 [100]。所以,此时 select 的结果还是小明1。所以,第二次 select 的结果和第一次 select 的结果一样,所以叫做 可重复读。

也就是说:

(1)已提交读隔离级别下的事务,在每次查询 select 的开始 都会重新生成一个独立的ReadView,获取最新的 当前活跃事务ID列表。

(2)可重复读隔离级别下的事务,只会在第一次查询 select 的时候生成一个ReadView,之后的查询 都复用之前的那个ReadView,不会获取最新的当前活跃事务ID列表。

6、当前读与快照读

6.1 当前读

当前读,也就是 加锁读,读取记录的最新版本,会 加锁 保证其他并发事务不能修改当前记录,直至获取锁的事务释放锁为止。

使用当前读的操作主要包括:【显式加锁的读操作、插入、更新、删除】等写操作,如下所示:

select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;

在执行这几种类型的操作时,会读取最新的记录,即使是别的事务已经提交的数据在本事务中也可以查询到。假设要 update 一条记录,但是 在另一个事务中已经 delete 掉这条数据并且commit 了,如果本事务做update就会产生冲突,所以在update的时候需要知道最新的数据。

6.2 快照读

快照度,也就是 不加锁读,读取记录的快照版本,而非最新版本,通过MVCC机制实现。

99、参考

(1)锁机制学习:https://blog.csdn.net/cmm0401/article/details/115860762

(2)事务机制学习:https://blog.csdn.net/cmm0401/article/details/115655095

(3)https://www.jianshu.com/p/615f3c7fbe6f

(4)https://baijiahao.baidu.com/s?id=1629409989970483292&wfr=spider&for=pc

(5)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值