MVCC 详解

MVCC 简单理解

MVCC,全称 Multi-Version Concurrency Control,是多版本并发控制的意思。

在高并发情况下操作数据库可能会出现脏写、脏读、不可重复度、幻读这四个问题。通过 MVCC 可以实现在不加锁的前提下避免一些问题。

MVCC 的实现原理

多版本

首先,我们引入一个概念,即行数据的版本。每次通过事务对行数据进行更新的时候,都会生成一个新的数据版本,并记录事务的唯一 id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。

下图记录了某一行数据的变更过程,图中虚线框里是同一行数据的 4 个版本,当前最新版本是 V4,k 的值是 22,它是被 transaction id 为 25 的事务更新的,因此它的 row trx_id 也是 25。

图中的虚线箭头,就是 undo log(回滚日志)。而 V1、V2、V3 并不是物理上真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 V2 的时候,就是通过 V4 依次执行 U3、U2 算出来。

一致性视图(Read View)与数据版本可见性规则

一个事务对某条记录进行读操作时,会查看这一条记录的一系列事务 id(由 undo log 构成的版本链中的事务 id),并根据事务的隔离级别("读已提交"、"可重复读")去选择生成 Read View 的方式,通过比较事务 id 来确定可见的版本。

实现原理是:在事务创建的时候,InnoDB 会构建一个视图数组用来存储"活跃"的事务的 id。这里的"活跃"是指:启动了但还没有提交。

该视图数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。

这个视图数组 + 高水位,就组成了当前事务的一致性视图(read-view)。

视图数组、低水位、高水位、当前事务 id 可记为:

  • m_ids:表示在生成 ReadView 时当前系统中活跃的事务(创建了但还没有提交的事务)的事务id列表。
  • min_trx_id:表示在生成 ReadView 时当前系统中活跃的事务中最小的事务 id,也就是 m_ids 中的最小值。
  • max_trx_id:表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
  • creator_trx_id:表示生成该 ReadView 的事务的事务 id。

是基于数据的 row trx_id 和这个一致性视图的对比结果,即可判断某一行数据的哪个版本是可见的。

数据版本可见性规则:

  • 如果被访问版本的 trx_id 值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。对应黄色区块。
  • 如果被访问版本的 trx_id 值大于或等于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。对应红色区块。
  • 如果被访问版本的 trx_id 值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。对应黄色区块。
  • trx_id 值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问,对应黄色区块;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问,对应绿色区块。

需要注意的是:上图的 id 并不是从左到右递增的,已提交的 id 可能会比活跃的 id 大。

不同隔离级别生成 Read View

不同隔离级别产生 Read View 的方式是不同的:

  • READ COMMITTED(读取已提交)------每次读取数据前都生成一个 Read View
  • REPEATABLE READ ------ 仅第一次读取数据时生成一个 Read View

快照读与当前读

我们前面提到的实际上都是 MVCC 中的"快照读",对应的就是最常见的不加锁的查询,如:

shell 复制代码
mysql> select k from t where id=1;

而事务中的 update 语句,使用的是"当前读。它用到了这样一条规则:更新数据都是先读后写的,而这个读,只能读当前的值,称为"当前读"(current read)。

因此,在下面的例子中,事务 B 读到的 k = 3。

事实上,如果把事务 A 的查询语句 select * from t where id=1 修改一下,加上 lock in share mode 或 for update,返回的 k 的值也会是 3。下面这两个 select 语句,就是分别加了读锁(S 锁,共享锁)和写锁(X 锁,排他锁)。

shell 复制代码
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

参考

相关推荐
m0_5719575832 分钟前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
一点媛艺2 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风2 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生3 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程3 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go