MySQL之事务学习——MVCC

本篇文章主要总结一下我对MVCC的理解以及知识总结。
在总结 undo log 时,undo log 的作用之一为实现mvcc,因此总结一下有关 mvcc 的知识。

一、什么是MVCC

MVCC(多版本并发控制),主要是为了提高数据库的并发性能,同一行数据平时发生读写请求时,会上锁阻塞住,但mvcc用更好的方式去处理读写请求,做到在发生读写冲突时不用加锁,这个读指的是快照读,而不是当前读,当前读是一种加锁操作,是悲观锁

二、当前读 VS 快照读

当前读(select … for update 等语句):它读取的数据库记录,都是当前最新的版本,当前读会对当前读取的数据进行加锁,防止其他事务修改数据。
快照读(普通select语句):快照读的实现是基于多版本并发控制,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是历史版本的数据。

三、Read View介绍

首先我们需要了解 ReadView 中的四个字段以及作用:

在这里插入图片描述
Read View 中的四个主要的字段:

  • create_trx_id:在创建当前 Read View 的事务的事务id;
  • m_ids:在创建 Read View 时,当前数据库中「活跃事务」的 事务id 列表,其中 “活跃事务” 指的就是开启了但还没提交的事务。
  • min_trx_id:在创建 Read View 时,当前数据库中「活跃事务」中事务id最小的事务,即m_ids的最小值。
  • max_trx_id:在创建 Read View 时当前数据库中应该给下一个事务的 id 值,即全局事务中最大的事务 id 值 + 1;

四、聚簇索引中的隐藏字段

在聚簇索引记录中,主要有两个隐藏列来结合 Read View 实现 MVCC :在这里插入图片描述

  • trx_id:当事务对某条聚簇索引记录进行改动时,会将该事务的 事务id 记录到 trx_id 隐藏列中。
  • roll_pointer:当聚簇索引记录进行修改时,会将旧版本的数据写入到 undo log 中,通过 roll_pointer指针,指向旧版本记录,因此可以通过这个指针找到修改前的数据。
MySQL中 事务id 的生成策略
事务id本质上就是一个数字,它的分配策略主要如下:
1、服务器会在内存中维护一个全局变量,每当需要为某个事务分配事务id时,就会把该变量的值当做事务id分配给该事务,并且把该变量自增1。
2、每当这个变量的值为256的倍数时,就会将该变量的值刷新到系统表空间中页号为5的页面中一个为Max Trx ID的属性中,这个属性占用8字节的存储空间。
3、当系统下一次重新启动时,会把这个Max Trx ID属性加载到内存中,将该值加上256之后赋值给前面提到的全局变量。
注:
1、分配事务id时,首先是先分配,再自增1。
2、重新启动后增加256是为了防止上次关机时,该全局变量的值可能大于磁盘页面中的Max Trx ID属性值。
3、该策略可以保证整个系统中分配的事务id值是一个递增的数字。先分配事务id的事务得到的是较小的事务id,后分配事务id的事务得到的是较大的事务id。

五、Read View的工作流程

Read View 与 聚簇索引中的 trx_id 结合使用,可以划分为三种情况:
在这里插入图片描述
当一个事务去访问记录时,除了本身事务的更新记录可见外,还包含如下几种情况:

  • 如果记录中的 trx_id 值 小于 Read View 中的 min_trx_id 值 ,则说明当前版本的记录是在创建该 Read View 前就已经提交的事务生成的记录,因此对该版本记录对当前事务是可见的。
  • 如果记录中的 trx_id 值 大于等于 Read View 中的 max_trx_id 值 ,则说明当前版本的记录是在创建该 Read View 后才开启的事务,因此该版本记录对当前事务是不可见的。
  • 如果记录中的 trx_id 值 在 Read Viewmin_trx_idmax_trx_id 之间,需要判断 trx_id 是否在 m_ids 列表中:
    • 如果记录的 trx_idm_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
    • 如果记录的 trx_id 不在 m_ids 列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

通过 Read Viewtrx_id 进行比较的方式,同时通过版本链来控制事务访问数据记录。

六、MVCC在可重复读的实现方式

在可重复读隔离级别下,启动事务时会生成一个 Read View,之后在整个事务执行期间都使用这个 Read View

假设当前数据库启动了两个事务,分别为事务A 和 事务B,其中 事务A 的 事务id 为 21,紧接着开启 事务B,事务B 的事务id 为 22,此时两个事务的 Read View 如下所示
在这里插入图片描述
上述两个事务的 Read View 中:

  • 事务A 的 Read View 中,事务id 为 21,因为当前开启的事务中只有 事务A,因此活跃事务的事务 id 列表只有21,其中活跃事务中的最小事务 id 为 事务A 本身,而`max_trx_id 为 22,即分配给下一个事务的事务 id 为22。
  • 事务B 的 Read View 中,事务id 为 22,此时活跃事务的事务 id 列表为 [21, 22],其中活跃事务中的最小事务 id 为 21,而 max_trx_id 为 23,即分配给下一个事务的事务 id 为23。

假设 事务A 和 事务B 在可重复读隔离级别下执行了如下的操作:

  • 事务 B 读取数据记录,读到 money 是 1000,即 事务B第一次读取记录。
  • 事务 A 将当前记录进行修改,修改 money 为 2000,此时并未提交事务;
  • 事务B 再次读取该记录,读到 money 仍然为 1000,即 事务B第二次读取记录。
  • 事务A 提交事务;
  • 事务B 再次读取该记录,读到 money 仍然为 1000,即 事务B第三次读取记录。

此时数据库中的数据情况如下:

  • 事务A还未进行记录修改时:
    在这里插入图片描述
  • 事务A修改记录之后:
    在这里插入图片描述

在经过 事务A 对记录的修改之后,以前的记录就变成了旧版本的记录,新版本的记录与旧版本的记录通过链表连接起来,此时新版本记录的 trx_id为21。

我们可以通过当前的数据记录中的 隐藏列 以及 两个事务生成的 Read View 分析一下 事务B 在读取记录的情况:

  • 当 事务B 第一次读取记录时,会先看当前记录的 trx_id,此时发现 trx_id 为20,与事务B 中 Read View 中的 min_trx_id 值(21)相比还小,说明修改这条记录的事务早在事务B开启时就已经提交了,因此该版本的记录对 事务B 可见
  • 当 事务B 第二次读取记录时,发现当前记录的 trx_id 值为 21,在事务B 的 Read View 中的 min_trx_idmax_trx_id 之间,则此时需要判断当前记录的 trx_id 是否在 m_ids 范围内,由 Read View 可知,m_ids 中包含 21,说明修改这条记录的事务还未提交,此时 事务B 读取不到该版本的记录。之后沿着 roll_pointer 往下查找旧版本的记录,直到找到 trx_id 小于 事务B 的 Read View 中的 min_trx_id 值的第一条记录,因此 事务B 第二次读取到的 money 值仍然为1000.
  • 事务B第三次读取记录时,由于当前的隔离级别为 可重复读,因此事务B使用的 Read View 仍然为事务开启时生成的 Read View 来判断当前版本的记录可否可见。因此,即使 事务A 修改记录并提交事务后,事务B 第三次读取记录时,读到的 money 值仍然为1000。

因此,在可重复读隔离级别下,整个事务在执行的过程中,使用的 Read View 一直为事务开启时生成 Read View

七、MVCC在读已提交的实现方式

在读已提交隔离级别下,MVCC 的实现 与 可重复读隔离级别相比:

  • 读提交 隔离级别是在每个 select 都会生成一个新的 Read View,这说明事务执行期间如果多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
  • 可重复读 隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。

在该隔离级别下,同样也是通过对比 记录中的 trx_id 与 事务的 Read View 中的字段进行比对,从而查询到可见的数据记录,比较的过程与上述在介绍可重复读隔离级别下读取记录的流程相似。

读已提交下,每次执行 select 语句都会生成新的 Read View,并且这期间可能会有许多事务的提交与事务的开启,同时这些事务会对记录进行修改,生成的 Read View 中字段值也可能随时发生变化,因此,在事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致的情况。

八、总结

MySQL为了提高数据库的并发性能,在进行快照读时采用MVCC的方式解决并发事务中的幻读问题,避免读取记录时需要上锁的情况。

事务在不同的隔离级别下,生成 Read View 的时机也是不同的:

  • 读提交 隔离级别是在每个 select 都会生成一个新的 Read View
  • 可重复读 隔离级别是启动事务时生成一个 Read View

MVCC在实现上,主要是通过聚簇索引记录中的 隐藏字段 与 事务生成的 Read View 中的字段进行比较,从而找到可见的数据记录。

以上就是我对于 MVCC 的一个总结,其中也参考了许多的资料后进行一个总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值