? ? ? ?拖了好久总算把这一节啃完了...做个记录,有一部分判断条件的案例还没想到,集齐之后可能会再加一篇案例。
一、 可见性判断
? ? ? ?回顾一下前面提到的SNAPSHOT_MVCC类型快照的可见性判断条件:
postgresql源码学习(十七)—— MVCC②-快照与隔离级别简介_Hehuyi_In的博客-CSDN博客_快照隔离级别
可见情况:
不可见情况:
- 在创建快照时尚活跃的事务
- 在创建快照后启动的事务
- 当前命令造成的变化(changes made by the current command)
下面以HeapTupleSatisfiesMVCC函数为例具体学习
二、 HeapTupleSatisfiesMVCC函数
? ? ? ?判断元组对于当前给定的MVCC快照是否可见,是则返回true,否则返回false。
? ? ? ?从前面的判断规则和函数代码可以知道,这个函数是基于可重复读级别判断的。
? ? ? ?由于这个函数分支实在太多,画了一幅流程图帮助理解:不同颜色的几个分区代表核心的几个条件分支;另外其中有很多类似的判断条件,标注了相同的颜色。
? ? ? ?如果有理解错误或者画得不对的地方还请指正~

函数代码如下:
? ? ? ?首先是流程图中蓝绿色区域,这部分的前提是元组xmin尚未提交,另外根据该元组是不是本事务写入的,又展开两个主要分支(蓝色区域)。
/*
* HeapTupleSatisfiesMVCC -- 如果元组(htup参数)对于给定的MVCC快照(snapshot参数)可见,则返回true,否则返回false
*/
static bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer)
{
HeapTupleHeader tuple = htup->t_data;
Assert(ItemPointerIsValid(&htup->t_self));
Assert(htup->t_tableOid != InvalidOid);
// A. xmin事务未提交(HEAP_XMIN_COMMITTED标记未设置)
if (!HeapTupleHeaderXminCommitted(tuple))
{
// xmin为INVALID,通常是写入失败导致的,直接返回false
if (HeapTupleHeaderXminInvalid(tuple))
return false;
// A. 元组xmin对应事务未提交
// A1. 本事务写入的元组(xmin是当前事务id)
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
{
/* xmin未提交,且xmin为本事务写入,说明是在同一个事务内的插入和查询命令,此时需要判断cid。同一个事务内,后面的查询可以查到当前事务之前命令插入且未删除的结果。
cid 表示在该事务中,执行当前sql之前还执行过几条sql,因此cid越大,执行顺序越靠后 */
/* A1-1. 元组cid >= 快照cid,说明元组是在获得快照后写入的,不可见(如何模拟?) */
if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
return false; /* inserted after scan started */
// 若不符合前面,说明元组是在获得快照前写入的,继续判断其可见性
/* A1-2.元组xmax是invalid,说明没有被删除,可见 */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
/* xmax在两种情况下被设置:对元组加锁;元组被删除。
A1-3.如果元组是在加锁的情况下设置了xmax,则可见 */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return true;
/* xmax是 MultiXactId的场景,这里仍然判断xmax的作用是加锁还是删除 */
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
xmax = HeapTupleGetUpdateXid(tuple);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
/* updating subtransaction must have aborted,执行更新的子事务必须已回滚(因为前提是xmin事务是未提交的,因此xmax必须已回滚)。 */
/* 若xmax不是当前事务id,返回可见(不是当前事务删除的,由于隔离级别是可重复读,即使其他事务删除后已提交,对本快照依然是可见的) */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
/* 当前事务在获取快照后做的元组update,可见*/
else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* updated after scan started */
/* 当前事务在获取快照前做的元组update,不可见*/
else
return false; /* updated before scan started */
}
// A1-4. xmax不是当前事务id 。xmax非MultiXactId,且非本事务删除的元组
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted,执行删除的子事务必须已回滚*/
/* 返回可见(不是当前事务删除的,由于隔离级别是可重复读,即使其他事务删除后已提交,对本快照依然是可见的) */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
return true;
}
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started,A1-5. 如果是在快照后做的元组delete,则可见*/
else
return false; /* deleted before scan started,A1-6. 如果是在快照前做的元组delete,则不可见*/
}
// XidInMVCCSnapshot用于判断xid对应事务是否仍在运行,若为true说明还在运行
// A.xmin事务未提交;
// A2.xmin事务仍在运行,不可见
else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
return false;
// A.xmin事务未提交;
// A3. xmin事务已提交,这里只设一个标记位,留到后面处理
else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
HeapTupleHeaderGetRawXmin(tuple));
// A4. 以上情况都不符合,说明xmin事务是异常终止的,则设置xmin为INVALID状态,返回不可见
else
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
InvalidTransactionId);
return false;
}
}
// xmin事务已提交
else
{
/* B. 即使xmin已提交,如果不是本事务写入的元组,由于隔离级别是可重复读,即使其他事务插入后已提交,对本快照依然不可见 */
if (!HeapTupleHeaderXminFrozen(tuple) &&
XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
return false; /* treat as still in progress */
}
? ? ? ?下面开始对应黄色区域,其前提都是xmin对应事务已提交(从指向黄色区域的两条线也可以看出),另外这部分的判断条件跟前面非常类似,可以对照流程图中相同颜色部分学习。

/* by here, the inserting transaction has committed,下面部分,插入的事务已提交,判断跟前面很类似 */
//C. xmin事务已提交
//C1.xmax无效,元组未被删除,可见
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted,未被删除,可见 */
return true;
// C2. xmax的作用是锁标记,可见
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
// xmax是MultiXactId
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
/* already checked above,再次检查 */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
/* 获取删除元组的事务id */
xmax = HeapTupleGetUpdateXid(tuple);
/* not LOCKED_ONLY, so it has to have an xmax,不是仅作为锁标记,所以必须有xmax */
Assert(TransactionIdIsValid(xmax));
/* 若删除元组的是当前事务(xmax是当前事务id) */
if (TransactionIdIsCurrentTransactionId(xmax))
{
/* 当前事务在获取快照后做的元组删除,可见*/
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
/* 当前事务在获取快照前做的元组删除,不可见*/
else
return false; /* deleted before scan started */
}
/* 若删除元组的不是当前事务,则检查它是否是快照中的活跃事务,如果是,则这个操作尚未提交,可见 */
if (XidInMVCCSnapshot(xmax, snapshot))
return true;
/* updating transaction committed,更新操作已提交,不可见 */
if (TransactionIdDidCommit(xmax))
return false;
/* it must have aborted or crashed,更新元组的事务异常终止,更新被回滚,元组仍可见 */
return true;
}
// C3. xmax非MultiXactId,且元组xmax未设置提交标记,需要判断可见性。
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
// C3-1. 本事务删除的元组(xmax是当前事务id)
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started,如果是在快照后做的元组delete,则可见 */
else
return false; /* deleted before scan started,如果是在快照前做的元组delete,则不可见 */
}
// C3-2. 非本事务删除
// xmax事务仍在运行中,可见
if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
return true;
// C3-3.xmax事务未提交,说明已回滚或者崩溃了,可见
if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
return true;
}
/* xmax transaction committed,以上判断均不成立,说明xmax对应事务已提交,可见 */
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetRawXmax(tuple));
}
// 否则,说明xmax已设置提交标记
else
{
/* xmax is committed, but maybe not according to our snapshot
C4. 同样由于可重复读隔离级别,非本事务修改的,即使xmax已提交也可以看到修改前的值 */
if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
return true; /* treat as still in progress */
}
/* xmax transaction committed,C5. xmax已提交,则不可见 */
return false;
}
参考
《PostgreSQL技术内幕:事务处理深度探索》第3章
PostgreSQL 源码解读(118)- MVCC#3(Tuple可见性判断)_ITPUB博客
openGauss事务机制中MVCC技术的实现分析_胡正策的博客-CSDN博客
https://developer.aliyun.com/article/281215
|