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 中的“快照读”,对应的就是最常见的不加锁的查询,如:
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 锁,排他锁)。
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;
参考
- MVCC到底是什么?这一篇博客就够啦
- [[01-知识点自测#2. 说下 MVCC]]
- MySQL 45 讲