一、官网描述
A consistent read means that InnoDB uses multi-versioning to present to a query a snapshot of the database at a point in time.
(google翻译:一致读取意味着 InnoDB 使用多版本控制向查询提供数据库在某个时间点的快照~)
二、目的
1. 分析consistent nonlocking read 获得的是一个什么样的快照
- 是事务中一直使用
- 还是每次查询都重新刷新获取
2. 分析在可重复读(Read-Repeatable)隔离级别下 with consistent snapshot 的作用
三、Read View 概念
InnoDB支持MVCC多版本,其中RC(Read Committed)和RR(Repeatable Read)隔离级别是利用Consistent Read View方式支持的。 就是在某一时刻给事务系统打一个快照(snapshot),把当时事务系统状态记录下来。之后的所有读操作根据其事务ID与快照中的事务系统的状态作比较,以此判断Read View对于事务的可见性。
查询数据时,根据事务ID所在区间判断数据可见性。事务ID是递增分配的,ReadView的机制在生成ReadView时确定了以下信息:
名称 | 描述 |
---|---|
m_ids | 表示在生成ReadView时,当前系统中活跃的读写事务的事务ID列表。[min_trx_id ... max_trx_id) |
min_trx_id | 表示在生成ReadView时,当前系统中活跃的读写事务中最小的事务ID,也就是m_ids中的最小值。 |
max_trx_id | 表示生成ReadView时,系统中将要分配给下一个事务的ID值。 |
creator_trx_id | 表示生成该ReadView的事务的事务ID 以上信息构成了如下事务ID区间段: |
以上信息构成了如下事务ID区间段:
当在事务中查询某一行数据时,具体流程如下
四、初始化表数据(存储过程)
delimiter //
create procedure t_init()
begin
drop table if exists t;
-- 表结构比较简单,就一个主键字段
create table t(id int primary key);
insert into t select 1;
insert into t select 2;
insert into t select 3;
end; //
delimiter ;
五、场景分析
场景1:tx_isolation = Read-Repeatable | ||
---|---|---|
时间线 | Session A | Session B |
t1 | call t_init(); | |
t2 | begin; | |
t3 | begin; | |
t4 | insert into t select 4; | |
t5 | commit; | |
t6 | select * from t; 结果: 1、2、3、4(SessionB提交的数据) |
|
疑惑点:在RR隔离级别下,sessionA为什么能查出来sessionB提交的数据?分明是sessionA先启动的事务,sessionB后启动的事务,先启动的事务能观察到后启动事务提交的数据,怎么现象跟RC(读已提交)一样? 答:innodb consistent nonlocking read (一致性无锁读),是innodb借助undo log(回滚段)和隐藏列实现的mvcc(多版本并发控制),从官网的定义可知,无锁读拿的是database的一个时间快照(a snapshot of the database at a point in time),但并不是sessionA执行了begin(或者start transaction)就立即可以拿到快照的,是sessionA执行的第一条无锁读时才拿到的(即在t6时),而在t5时,sessionB已经提交(commit)了,因此在t6时sessionA在拿database的快照的快照里,包含了id=4这条记录。 |
场景2:tx_isolation = Read-Repeatable | ||
---|---|---|
时间线 | Session A | Session B |
t1 | call t_init(); | |
t2 | begin; | |
t3 | begin; | |
t4 | select * from t for update; 查询结果:1、2、3 |
|
t5 | insert into t select 4; | |
t6 | commit; | |
t7 | select * from t; 查询结果: 1、2、3、4(SessionB提交的数据) |
|
t8 | commit; | |
疑惑:t4时,SessionA查到的结果是1、2、3无可厚非,但在t7时,查询的结果也应该是1、2、3才对呀!因为SessionA在t4时执行了查询操作,这时候就拿到了database的快照了,t7应该和t4使用同一个快照呀! 答: t4时,SessionA并没有拿到database snapshot,因为执行的是一个锁读(consistent locking read),后面跟了 for update,而锁读跟MVCC无任何关系,更不可能拿到快照了。SessionA真正拿的快照的时刻是t7(这个时刻执行了无锁读),而在t6时SessionB已经commit了,因此t7时,SessionA拿到的快照里包含了id=4这条记录。 |
场景3:tx_isolation = Read-Repeatable | ||
---|---|---|
时间线 | Session A | Session B |
t1 | call t_init(); | |
t2 | start transaction with consistent snapshot; | |
t3 | begin; | |
t4 | insert into select 4; | |
t5 | commit; | |
t6 | select * from t; 查询结果: 1、2、3 |
|
疑惑: 看起来执行的语句跟场景1的是一样的,为啥t6时,SessionA为啥查询不到id=4的记录? 答:注意到SessionA开户事务的语句是 with consistent snapshot,这表示在开启事务时就拿到了database的快照,即SessionA在t6时刻使用的快照与t2时刻的是同一个。 |
六、总结
1. MVCC、snapshot of database 是 innodb consistent nonlock read(一致性无锁读)时候的概念,而与innodb locking read(锁读)不产生关系。
2.事务拿database快照的时刻有两种:
- 一种是执行第一条无锁读的时候,
- 一种是开启事务时带 with consistent snapshot
3.快照指的是database在某个时刻的快照,不仅仅指的是单个(或某几个)记录的快照。
4.在RR隔离级别下,在同一个事务中,若拿到了快照,后续执行会一直使用同一个快照(这点与RC不同)。