查询流程:
以 Select c2 from tb1 where c1 = 1为例:
1、根据索引找到TID(1,2)的元组指针,即linePointer。
2、计算其事务xid是否可见,在Map中计算其csn和快照csn的大小关系。
3、如果大于快照CSN,那么不可见,判断还有没有undo链,如果有,执行第4步,如果没有,返回查询无结果;如果可见,返回。
4、查看元组的TD字段,得到该元组上一个版本时对应的事务槽索引td_id,从该链首位置开始遍历,遍历流程见第5步。
5、然后从对应的事务槽中不断按照链表的形式,从新往旧找(按照block pre pointer指针),直到找到第一个满足页块号和偏移量等于TID(1,2)的元组,执行第2步。
为什么这样查找?
1、事务槽是存在复用的,并不是一个事务槽始终被一个事务占据,所以一个元组在不断更新的操作中,可能会对应不同的事务槽,也就是需要从不同的undo链中跳来跳去,直到找到满足元组指针(偏移量)和可见性都满足的元组。
2、当一个事务修改元组时,事务在一个事务槽中,会把旧元组移到该事务槽对应的undo链中,然后在堆表中产生一个新元组。也就是说如果要找到一个堆表中元组的上一个版本,那么必然在其指向的事务槽undo链中!
为什么每次切换回滚链都从链首开始回滚,能够保证在寻找历史版本数据时不会遍历同一个回滚链两次?
答:同一个事务有没有可能在同一条链上产生多个历史版本数据呢?答案是可能的,比如
一个事务T1(槽1)执行了1次更新操作;
事务T2(槽2)又对其执行了一次更新;
T3占据了和T1同样的事务槽(槽1),执行了一次更新;
T4占据了槽2,执行了一次更新。
同时在遍历过程中,有可能出现找到一半就跳转去另外一个链的情况。这样两条链之间跳来跳去,为了避免出现死循环,在判断undo_record时,除了要确保blkno和offset相同,还需要确保undo_record的xid互相一致。具体操作时在切换到一个新链时,一直往上寻找的过程中必须找到xid与上次找到的undo_record中的OldXid相同的undo_record。
示例:
CREATE TABLE t(id int,age int) WITH (INIT_TD=2);
begin;
insert into t values(1,1);
update t set age=45 where id=1;
commit;
tdslot=1
长事务
start transaction isolation level repeatable read;
select * from t where id=1;
这一步仅查询不会分配事务目录
update t set age=67 where id=1;
tdslot=2
update t set age=84 where id=1;
tdslot=1
update t set age=101 where id=1;
tdslot=1
update t set age=121 where id=1;
tdslot=2
此时在长事务中执行查询操作就会发现上文中出现的逻辑