1 背景 读提交和可重复读依赖MVCC
有个背景大家都不陌生,当多个事务对相同的数据行进行操作时,会出现各种并发问题。MySQL通过四种隔离级别来解决这个问题。
1.1 读未提交:read uncommitted 隔离级别是最松散的,基本上不做任何隔离,所以实现起来非常简单。
每次执行一条语句(包括查询和更新语句)时,都会生成一个一致的视图,以保证当前事务可以看到其他事务提交的数据。
每个事务在打开时都会生成一个一致的视图。 当其他事务提交时,不会影响当前事务中的数据。 为了保证这一点,MySQL是通过多版本控制机制MVCC来实现的
通过加锁来实现的,所以MySQL有一套加锁机制。
总之,读提交和可重复读隔离级别都依赖于 MVCC 多版本控制机制的实现。下面将讨论 MySQL 中的 MVCC 多版本控制机制。
2 MVCC多版本控制工作原理
MVCC机制采用read-view机制和undo log版本链比较机制,不同的事务会根据数据版本链比较规则读取版本链上相同数据的不同版本。
2.1 undo log 版本链
当一个事务打开时,首先会申请一个事务id:transaction-id;
当一个事务修改一行数据时,Mysql会保留修改前数据的undo回滚日志,并将事务id:transaction id赋值给版本记录中的字段trx_id。
注意这里记录的版本并不是真实的物理存在。 只有真实物理存在的最新记录,其他历史记录来源于回滚日志。
2.2 Read-view 机制
可重复读隔离级别和读提交隔离级别是通过生成一致的视图来实现的,即read-view。
当一个事务启动时,InnoDB 为该事务构造一个数组来存储在事务启动时处于活动状态的所有事务 id。 活动意味着它已启动,但未提交。
数组中id的最小值为低位,最大值+1记为高位,即一致性视图。(极端情况下可以出现紫色部分为空,即一个事务自mysql启动后一直没有提交。)
当每个事务进行查询时,都会根据一致性视图的可见性规则推导出undo log版本链中对应的数据。
3 一致性视图的可见性规则
- 如果当前事务 id 落在紫色部分,说明这个版本是一个已提交的事务或者是当前事务自己生成的,并且这个数据是可见的。
- 如果当前事务id落在蓝色部分,说明这个版本是由未来开始的事务生成的,肯定是不可见的。
- 如果当前交易id落在橙色部分,则包括两种情况:
- A、如果trx_id行在数组中,说明这个版本是由一个还没有提交的事务产生的,不可见。 B、如果trx_id行不在数组中,则说明这个版本是由一个提交的事务生成的,可见的。
· 3.1 case1
图中事务A查询的i是什么? 先来分析一下。
按照事务从上到下打开的顺序,每个事务对应的一致性视图如下:
- 事务 A [11] 的一致性视图数组
- 事务 B 的一致性视图数组 [11,12]
- 事务 C [11, 12, 13] 的一致性视图数组
在查询事务A的那一刻,undo log版本链为:({} 代表版本记录。>>> 代表回滚日志undo log。)
{trx_id=11,id=1,i=10,roll_pointer=0}>>>{trx_id=13,id=1,i=11,roll_pointer=1}>>>{trx_id=12,id=1,i=12,roll_pointer=2}
- 查询事务 A 时,事务 B 和事务 C 属于未来事务,对事务 A 是不可见的。
- 所以事务A查询到的数据是根据undo log通过最新的数据记录不断前滚和回滚得到的数据:i=10。
3.2 case2
事务 A 查询 1 的结果是什么?
事务 A 查询 2 的结果是什么?
根据一致性视图可见性规则分析,对于事务A,事务B是未来事务,对事务A是不可见的,所以查询结果i=10。
查询 2 的结果是 i = 12,为什么? 我们先来看两个概念。
- 可重复读:在可重复读隔离级别中,通过回滚日志找到对应版本记录的读方法是一致性读。
- 当前读:不是回滚,你只需要读取最新版本的记录就是当前读取的。
如果事务中有更新语句,则更新语句以当前读取方式读取版本记录中的最新数据,然后进行更新操作,所以上图中的查询结果为i = 12。
当前读还包含以下两种查询方法:
- select k from t where id=1 lock in share mode;
- select k from t where id=1 for update;
以上就是MVCC机制。
根据其规则,该机制仅在可重复读隔离级别和读提交隔离级别下可用。