一、什么是快照?
当运行 select 查询语句时,才会触发快照,创建 read view 对象,把此时正在处理(未提交)的事务的 ID 都记下来,以便于后面查询时可以控制该读哪些事务的记录,不该读哪些事务的记录。
二、read view 的结构
class ReadView
{
private:
m_ids; // 存处于快照区间内的事务 ID,即此时还在运行的事务 ID
low_limit_id; // 此时系统还未分配的最小事务 ID,即目前已出现过的事务 ID 的最大值 + 1
up_limit_id; // m_ids 中最小的事务 ID
creator_tx_id; // 创建读视图的事务 ID
};
三、如何用 read view 来访问规定的文件?
由于事务和进程一样,都是并发(交替)执行的,因此早来的事务未必早提交,晚来的事务未必晚提交;晚来的事务早提交也是有可能的。 因此当遍历历史版本链时,所访问到的事务 ID 就分以下几种情况:
- ID < up_limit_id
此时就说明该事务是在 select 之前就已经提交了,可以访问。
- up_limit_id <= ID < low_limit_id
因为此时是在历史版本链里访问到的事务 ID,因此该事务肯定是已经提交了的。但是事务 ID 在 low_limit_id 和 up_limit_id 之间,因此说明该事务是和 m_ids 里的事务一起进来的(即是在 select 时进来的),但是由于该事务在 select 之前就执行完了,因此就提交了。因此是可访问的。
举个例子:现在分别有 1,2,3,4 号事务,在 select 之前,4 号事务因为是短事务,就执行完了,因此 4 号事务就提交并加入到历史版本链里了。但此时 1,2,3 号事务都还没执行完,因此 read view 中的 m_ids 数组为:{1, 2, 3};low_limit_id 为:4 + 1 = 5;up_limit_id为:min(m_ids) = 1。因此 up_limit_id <= 4 < low_limit_id,而且因为 4 号事务在 select 前就提交了,因此 4 号事务是可访问的。所以,我们可以得出一个结论:如果处于 up_limit_id <= ID < low_limit_id 这一区间的 ID 是已提交状态,那么肯定是 select 之前就已经提交了。
或者,你也可以这样理解,4 号事务是跟 1,2,3 号事务一起开始运行的,但快照(select)的时候并不在 m_ids 数组里,那肯定是 4 号事务运行完提交了才不会在 m_ids 里的。
- low_limit_id <= ID
因为此时是在历史版本链里访问到的事务 ID,因此该事务肯定是已经提交了的。但 up_limit_id <= ID,说明该事务是在 select 之后才进来的,因此也是 select 开始之后才提交的,因此是不可访问的。
四、如何通过 read view 实现隔离等级? (Read committed & Repeatable Read)
a. Read committed
每次 select 都会进行一次快照读。
b. Repeatable read
每次 select 只会采用最开始 select 那次的快照读。