一致性视图的创建时间
start transaction
并不是一个事务的起点,在执行到它之后的第一个操作InnoDB表的语句,事务才真正启动。如果要立即启动一个事务,可以使用start transaction with consistent shapshot
;- 在使用
start transaction
时,一致性视图是在执行第一个快照读语句是创建的; - 使用
start transaction with consistent shapshot
时,一致性视图是在该语句执行时就创建了;
两种视图
- 普通视图:一个用查询语句定义的虚拟表,在调用的时候执行查询语句生成结果;
- 一致性读视图:InnoDB的MVCC是使用一致性读视图实现的,该视图是RC和RR隔离级别实现的基础;
事务快照的实现
InnoDB中每个事务都有一个唯一的transaction id,是按照顺序严格递增的;
每行数据会有多个版本,每次有事务更新该行数据时,就会生成一个新的数据版本,并将该事务ID记录到该版本的row trx_id中,表示该版本是由哪个事务产生的;同时旧版本也会保留,并且可以通过新版本访问旧版本数据;(链表?)
行数据版本示例:
说明:- 图中在物理上只存在V4,也就是最新版本,V1、V2和V3都是逻辑上存在的,是通过Undo日志计算出来的;
- 也就是说物理上,每行数据只会保留其最新版本,及所有未提交事务产生的Undo日志;
可重复读:事务启动时,能够看到所有已提交的事务的结果,能够看到事务内的结果,但在该事务执行期间,其他事务的更新对其不可见;
事务启动时,InnoDB生成一个数组,保存事务启动时所有处于active状态的事务的ID;数组中的最小事务ID为低水位,当前系统中已经创建过的最大事务ID加1为高水位;
该视图数组以及高水位,一起构成了一个该事务的一致性读视图;
对于当前事务来说,数据版本的可见性规则如下:
- 如果数据版本中的row trx_id小于低水位,表示该数据已提交,则该数据版本对当前事务来说是可见的;
- 如果数据版本中的row trx_id等于当前事务ID,表示数据版本是当前事务产生的,则该数据版本对当前事务来说也是可见的;
- 如果数据版本中的row trx_id大于等于高水位,表示数据版本是未来某个事务产生的,则该数据版本对当前事务来说不可见;
- 如果数据版本中的row tx_id在低水位和高水位之间,此时如果row trx_id在当前事务的视图数组中,表示该数据版本是由未提交的事务产生的,则该数据版本对当前事务来说不可见;如果row trx_id不在当前事务的视图数组中,表示该数据版本是由已提交的事务产生的,则该数据版本对当前事务来说是可见的;
基于以上规则,实现了事务的快照;
InnoDB 利用了”所有数据都有多个版本”的这个特性,实现了秒级快照;
示例
表结构如下:
1
2
3
4
5
6mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);事务信息如下:
问题:
- 事务A的查询结果?
- 事务B的查询结果?
分析
- 假设id=1的数据在事务ABC开始前,row trx_id = 90;
- 假设事务ABC的transaction id分别为100,101,102;
- 假设事务A开始前还有active 的事务,其id = 99,且再没有其他事务;
- 则按照之前的一致性视图描述,事务A的一致性视图数组为[99, 100],其中低水位为99,高水位为101;
- 同理可得事务B的一致性视图数组为[99,100,101],其中低水位为99,高水位为102;
- 同理可得事务C的一致性视图数组为[99,100,101,102],其中低水位为99,高水位为103;
- id=1的数据的版本列表为:最新版本的row trx_id = 101,次新版本的row trx_id = 102,之后是row trx_id = 90;
- 则按照RR隔离级别下可见性规则,可知,事务A在查询时,首先发现最新版本101等于其视图数组的高水位101,则该版本不可见,继续找旧版本;接着发现102大于其高水位101,也不可见,最后发现90小于其低水位99,可见,得到最后查询结果是1;
- 事务B在查询开始时,按照可见性规则,可知,事务B发现最新版本101等于当前事务ID 101,则是该事务内更新,可见,得到最后查询结果是3;
其他点
- 数据更新时是先读后写的,这里的读,是“当前读”,即读当前时刻数据版本的最新值;
- 当有多个事务同时读数据最新值时,需要加锁来保证顺序;比如:如果示例中的事务C没有立即提交,而是在事务B的更新和提交之间再提交,则事务B的更新就会由于需要等待事务C的提交而卡住(等待事务C释放id=1的行锁);
总结
RR隔离级别的核心是一致性读视图;
事务在执行更新时需要执行当前读,如果当前有其他事务占据了行锁,则需要等待;
RR隔离级别在事务开始时就创建了一致性读视图,之后事务中的其他语句都共享该一致性读视图;
RC隔离级别在事务的第一个语句开始执行时才创建一致性读视图,之后事务中每条语句执行前都会重新计算一遍一致性读视图;
- 在RC隔离级别下再分析一下示例;
- 显然数据的版本依旧是101–> 102 –> 90;
- 事务A在执行查询时的一致性视图为[99, 100, 101],低水位是99,高水位是103;则发现数据版本101在一致性视图数组中,且与当前事务ID不等,属于未提交事务,则不可见,继续找102版本,发现102版本不再一致性视图数组中,且小于高水位,属于已经提交的版本,可见,得到结果为2;
- 事务B的查询时的一致性视图为[99, 100, 101], 低水位是99,高水位是103;则发现数据版本101在一致性视图数组中,且与当前事务ID相等,可见,得到结果为3;
对于表结构,由于其没有对应的行数据,所以没有row trx_id字段,只能遵循当前读,也就是读最新版本;