MySQL一致性无锁读原理及场景分析

2023-09-18 15:10 雾和狼 832

一、官网描述

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不同)。