快照有了,就可以通过快照数据去判断元组的可见性。回顾一下(先不考虑子事务),事务的关键信息:元组头部事务信息、快照信息、clog事务状态(SetHintBits前需要)
- 元组上有元组xmin、xmax、cmin、cmax、infomask等
- 快照数据中有快照xmin、xmax、xip_list、curcid等
- clog中额外的事务状态信息,也可能写入了infomask中的hintbits
快照类型不同,可见性判断略有区别
bool
HeapTupleSatisfiesVisibility(HeapTuple tup, Snapshot snapshot, Buffer buffer)
{
switch (snapshot->snapshot_type)
{
case SNAPSHOT_MVCC:
return HeapTupleSatisfiesMVCC(tup, snapshot, buffer);
break;
...
case SNAPSHOT_NON_VACUUMABLE:
return HeapTupleSatisfiesNonVacuumable(tup, snapshot, buffer);
break;
}
...
}
每种快照都有各自的可见性规则,这里用最常见的SNAPSHOT_MVCC快照可见性规则来理解元组可见性
static bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer)
{
HeapTupleHeader tuple = htup->t_data;
Assert(ItemPointerIsValid(&htup->t_self)); //lp有效,也就是元组有效
Assert(htup->t_tableOid != InvalidOid); //oid有效,也就是表还有效
//t_xmin未提交,insert或update新元组的事务,未提交
//在htup_details.h中已经宏定过,HeapTupleHeaderXminCommitted()是((tup)->t_infomask & HEAP_XMIN_COMMITTED) != 0
//也就是说if (!HeapTupleHeaderXminCommitted(tuple)) 表示元组infomask中没有HEAP_XMIN_COMMITTED
//其实就是字面含义,t_xmin没有提交
if (!HeapTupleHeaderXminCommitted(tuple))
{
//假如有个事务更新了元组,但是回退或者失败了,那么这个元组的xmin就是失败的事务ID
//如果是失败事务的t_xmin,则直接返回不可见
if (HeapTupleHeaderXminInvalid(tuple))
return false;
//当元组infomask有HEAP_MOVED_OFF标记时,vacuum元组单独判断可见性,并对于vacuum事务做一些hintbits标记
/* Used by pre-9.0 binary upgrades */
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsCurrentTransactionId(xvac))
return false;
if (!XidInMVCCSnapshot(xvac, snapshot))
{
if (TransactionIdDidCommit(xvac))
{
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
InvalidTransactionId);
return false;
}
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
InvalidTransactionId);
}
}
//当元组infomask有HEAP_MOVED_IN标记时,vacuum元组单独判断可见性,并对于vacuum事务做一些hintbits标记
/* Used by pre-9.0 binary upgrades */
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
if (XidInMVCCSnapshot(xvac, snapshot))
return false;
if (TransactionIdDidCommit(xvac))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
InvalidTransactionId);
else
{
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
InvalidTransactionId);
return false;
}
}
}
//当元组是本事务写入的
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
{
if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid) //当元组cid>=快照事务当前commandid时
return false; // 说明插入元组的时间晚于可见性检测开始时间,元组不可见
if (tuple->t_infomask & HEAP_XMAX_INVALID) //当元组infomask位有HEAP_XMAX_INVALID时
return true; //说明元组没有被删除(delete),元组可见
//因为仅插入元组,不提交、提交或者回退,都是HEAP_XMAX_INVALID
//但是这个判断在“本事物写入”条件下,所以这里的逻辑是
//本事务新增元组,未提交(逻辑上等价于同一事务中元组未被删除),且t_cid<curcid,元组可见
//xmax在两种情况被设置:1对元组加锁,元组被删除
//即使元组没有HEAP_XMAX_INVALID,可能也不是被删除,也可能是元组加锁了
//元组加锁情况下设置了xmax,元组可见
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return true;
// HEAP_XMAX_IS_MULTI是对多个事务获取同一行锁时,才会产生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 */
//如果xmax不是当前事务,则可见
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
//如果xmax是当前事务,通过commandid判断,在更新和删除操作之前获得快照,在获得快照时间元组是可见的
else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* updated after scan started */
else
return false; /* updated before scan started */
}
//以下判断场景是:子事务中的删除命令回退了,需要SetHintBits为HEAP_XMAX_INVALID
//删除命令回退了,所以元组是可见的
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
return true;
}
//cmax删除元组的cid
//如果元组cmax>=快照curcid,则删除发生在快照扫描之后,元组可见
//如果元组cmax<快照curcid,则删除发生在快照扫描之前,元组不可见
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
//XidInMVCCSnapshot()判断xid在快照生成时是in-progress状态
//in-progress是 1.快照xmin<=xid<快照xmax且xid in xip_list 2.xid>=快照xmax
//以下XidInMVCCSnapshot()中的xid是t_xmin
//所以,这个判断的含义是:如果t_xmin在快照生成时是in-progress状态,则元组不可见
//相当于t_xmin没有提交,所以元组不可见。这看上去有点奇怪
//因为整个判断是在!HeapTupleHeaderXminCommitted(tuple)下的,含义也是t_xmin没有提交,判断有些重复
//但是加上前面的几个小判断,这的else if就变得合理,其含义为:
//t_xmin没有提交,且元组没有被删除,且不是当前事务,则元组不可见
else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
return false;
//如果t_xmin事务提交了,做个SetHintBits为HEAP_XMIN_COMMITTED
//这里看上去有点奇怪,整个判断是t_xmin未提交的情况下,不应该出现t_xmin提交了
//而且,真的有这种情况的话,为什么这里不做事务可见性判断?
else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
HeapTupleHeaderGetRawXmin(tuple));
//如果t_xmin事务没有提交,做个SetHintBits为HEAP_XMIN_INVALID
else
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
InvalidTransactionId);
//t_xmin事务没有提交,再次返回不可见。看上去跟上面的关于XidInMVCCSnapshot()的条件很像
//当前即没有提交,又不满足XidInMVCCSnapshot()(xid在快照生成时不是in-progress)
//只有生成快照时事务未开始,后来事务开始了,目前没有提交,元组不可见
return false;
}
}
//xmin未提交的情况下的事务可见性总算判断完了
//这里的else以后都是xmin提交的情况下,对元组可见性的判断
//xmin已提交的判断是hintbits位有HEAP_XMIN_COMMITTED
else
{
//xmin已提交,但是不是当前快照做的
/* xmin is committed, but maybe not according to our snapshot */
//当infomask没有HEAP_XMIN_FROZEN且在快照生成时xmin in-progress状态,则元组不可见
//整合翻译一下,这里的if含义为快照生成时xmin未提交,在可见性判断时,元组xmin提交但没有标记FROZEN的情况,元组不可见
//即使元组xmin已提交了,对当前快照而言仍然是in-progress
if (!HeapTupleHeaderXminFrozen(tuple) &&
XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
return false; /* treat as still in progress */
}
//HEAP_XMAX_INVALID表示元组没有被删除
//这里的if含义是:当前元组提交了且在快照生成时也提交了且没有被删除(完全没有删除标记),元组可见
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
return true;
//虽然元组上有xmax,但不是删除事务,而是锁标记
//这里的if含义是:当前元组提交了且在快照生成时也提交了且有xmax,但xmax是锁标记,元组可见
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
//HEAP_XMAX_IS_MULTI表示元组处于shared-row-lock,一般表示多个事务处理一行时行所拥有的infomask标记
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 */
Assert(TransactionIdIsValid(xmax));
//如果shared-row-lock元组的事务ID是当前事务
if (TransactionIdIsCurrentTransactionId(xmax))
{
//元组cmax>=快照curcid时,元组在快照生成时还没有被删除,元组可见
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
//元组cmax<快照curcid时,元组在快照生成时还已被删除,元组可见
else
return false; /* deleted before scan started */
}
//如果shared-row-lock元组的事务ID不是当前事务,且在快照生成时xmax处于in-progress状态
//这里的if含义是:xmin提交,且元组未被删除,有MULTI XMAX标记的情况下,xmax在快照生成时还未提交,元组可见
if (XidInMVCCSnapshot(xmax, snapshot))
return true;
//如果shared-row-lock元组事务已提交,则元组不可见
if (TransactionIdDidCommit(xmax))
return false; /* updating transaction committed */
/* it must have aborted or crashed */
//更新元组异常终止或回滚,元组仍可见
return true;
}
//元组xmin已提交,xmax还没有提交标记,还没有被删除
//看来!HEAP_XMAX_COMMITTED和HEAP_XMAX_INVALID还是有点区别
//这里的判断看上去像元组经历过删除,但删除事务没有提交
//而上面的HEAP_XMAX_INVALID是完全确认没有删除元组或删除abort or rollback,所以可以直接判断为true
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
//如果xmax与校验事务是同一事物
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
//还是老一套通过commandid检测可见性
//cmax>=快照curcid,说明删除在快照生成之后,元组可见
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
//cmax<快照curcid,说明删除在快照生成之前,元组不可见
else
return false; /* deleted before scan started */
}
//删除事务没有提交,且xmax与校验事务不是同一事物
//xmax在生成快照时是in-progress的,则元组可见
if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
return true;
//确认xmax删除事务回退或失败,SetHintBits为HEAP_XMAX_INVALID
//类似上面的HEAP_XMAX_INVALID,元组可见
if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
return true;
}
/* xmax transaction committed */
//还剩下xmax删除事务已提交的场景,SetHintBits为HEAP_XMAX_COMMITTED
//按道理这里应该判断可见性,但是没有写在这,而是在代码的最后几行。因为这里的小条件属于大条件之一
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetRawXmax(tuple));
}
else
{
/* xmax is committed, but maybe not according to our snapshot */
//xmax删除事务当前已提交,但是在快照生成时in-progress,则元组可见
if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
return true; /* treat as still in progress */
}
/* xmax transaction committed */
//只剩下xmax已提交的,且不在快照生成时in-progress的情况,元组不可见
return false;
}
整个可见性判断的源码看上去有点复杂。把SetHintBits部分刨去,省略繁杂的if,只看关键性的可见性规则,其中的关键点如下: