引言
在上一篇博客MVCC(3)–事务可见性中,我们分析了判断数据有效性、可见性、可更新性的算法的实现两个主要函数中的 本篇博客将会分析两大主要函数中的另一个—HeapTupleSatisfiesMVCC 文件路径:src\gausskernel\storage\access\heap\heapam_visibility.cpp
HeapTupleSatisfiesMVCC
一、简化的HeapTupleSatisfiesMVCC流程图
HeapTupleSatisfiesMVCC`的函数,它用于检查一个堆元组(Heap Tuple)是否满足多版本并发控制(MVCC)的条件。这是数据库事务处理中的一种常见机制,用于处理并发读写操作。
函数接收三个参数:
htup
(一个堆元组),snapshot
(一个快照,通常包含了某一时刻的数据库状态),- 以及
buffer
(一个缓冲区,通常用于存储临时数据或者是待处理的数据)。 其处理过程大致分为如下图所示的三个模块
如上图
- 函数首先进行一些基本的断言检查,以确保输入的堆元组是有效的。
- 然后,它会根据不同的条件检查元组是否对当前的事务可见。这包括检查元组是否被当前- 事务修改,是否被其他事务修改,以及这些事务的状态(是否已提交或中止)。
- 如果元组满足所有条件并对当前事务可见,则函数返回
true
;否则,返回false
。 下面我们将对事务可见性检查的内容做重点解读。
二、事务可见性部分详细分析
事务可见性判断代码块中保证了数据一致性和隔离性,我们对其的重要步骤做逐步分析。 这些步骤大体上是对元组进行一系列复杂的并发控制和事务隔离检查,并根据检查结果进行相应的处理。
调用HeapTupleHeaderXminCommitted(tuple))判断是否元组的xmin还没有提交。
-
调用HeapTupleHeaderXminInvalid(tuple)判断元组的有效性
-
判断是否当前的快照不是一个多版本并发控制(MVCC)的快照,并且元组的xmin是当前事务的ID。
-
对元组状态的检查和处理。
XidVisibleInSnapshot函数检查元组的xmin在给定的快照中是否可见。然后根据这个函数的返回结果和一些其他条件,可能会返回false,或者设置一些提示位。
简化后的事务可见性模块流程图如下所示
由于上图对红、橙、黄三个矩形所代表的模块进行了简化表达,接下来我们对这三个方框的内容进行补充说明。
元组检查
例如,检查元组是否在扫描开始后被插入或删除,检查元组的xmax是否是一个多事务ID,检查元组的xmax是否是当前事务的ID等等。根据这些检查的结果,可能会返回true或false,或者设置一些提示位。
-
判断元组的xmax是否有效
-
判断是否元组只被锁定,而没有被删除。
-
元组是否为被多事务共享
-
元组的信息掩码与
HEAP_XMAX_COMMITTED
进行位与运算的结果为0。如果满足对元组进行一些更复杂的检查和处理。 -
元组的xmax在给定的快照中是否可见。
快照可见检查
-
元组的xmin(也就是该元组被插入或更新的事务ID)在给定的快照中是否可见,并将结果存储在visible变量中。返回一个提示状态,存储在hintstatus变量中。
-
如果提示状态表示xmin对应的事务已经提交,那么就设置元组的提示位为HEAP_XMIN_COMMITTED。
-
如果提示状态表示xmin对应的事务已经中止,那么进行一些更复杂的检查和处理。
-
如果元组的xmin在给定的快照中不可见,那么进入下面的代码块,在这个代码块中,会进行一些更复杂的检查和处理。
recheck_xmax检查大致同上,此处不再赘述
三、源码以及完整注释
文件路径:src\gausskernel\storage\access\heap\heapam_visibility.cpp
static bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)
{
HeapTupleHeader tuple = htup->t_data;
Assert(ItemPointerIsValid(&htup->t_self));
Assert(htup->t_tableOid != InvalidOid);
bool visible = false;
TransactionIdStatus hintstatus;
Page page = BufferGetPage(buffer);
ereport(DEBUG1,
(errmsg("HeapTupleSatisfiesMVCC self(%d,%d) ctid(%d,%d) cur_xid %ld xmin %ld"
" xmax %ld csn %lu",
ItemPointerGetBlockNumber(&htup->t_self),
ItemPointerGetOffsetNumber(&htup->t_self),
ItemPointerGetBlockNumber(&tuple->t_ctid),
ItemPointerGetOffsetNumber(&tuple->t_ctid),
GetCurrentTransactionIdIfAny(),
HeapTupleHeaderGetXmin(page, tuple),
HeapTupleHeaderGetXmax(page, tuple),
snapshot->snapshotcsn)));
/*
* Just valid for read-only transaction when u_sess->attr.attr_common.XactReadOnly is true.
* Show any tuples including dirty ones when u_sess->attr.attr_storage.enable_show_any_tuples is true.
* GUC param u_sess->attr.attr_storage.enable_show_any_tuples is just for analyse or maintenance
*/
if (u_sess->attr.attr_common.XactReadOnly && u_sess->attr.attr_storage.enable_show_any_tuples)
return true;
if (!HeapTupleHeaderXminCommitted(tuple)) {
if (HeapTupleHeaderXminInvalid(tuple))
return false;
/* IMPORTANT: Version snapshot is independent of the current transaction. */
if (!IsVersionMVCCSnapshot(snapshot) &&
TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
if ((tuple->t_infomask & HEAP_COMBOCID) && CheckStreamCombocid(tuple, snapshot->curcid, page))
return true; /* delete after stream producer thread scan started */
if (HeapTupleHeaderGetCmin(tuple, page) >= snapshot->curcid)
return false; /* inserted after scan started */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask, tuple->t_infomask2)) /* not deleter */
return true;
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
TransactionId xmax = HeapTupleHeaderMultiXactGetUpdateXid(page, tuple);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
else if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
return true; /* updated after scan started */
else
return false; /* updated before scan started */
}
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
/* deleting subtransaction must have aborted */
Assert(!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple)));
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return true;
}
if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
} else {
visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, &hintstatus, buffer, NULL);
if (hintstatus == XID_COMMITTED)
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));
if (hintstatus == XID_ABORTED) {
if (!LatestFetchCSNDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
snapshot,
"HeapTupleSatisfiesMVCC set HEAP_XMIN_INVALID xid don't abort");
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
}
if (!visible) {
if (!GTM_LITE_MODE || u_sess->attr.attr_common.xc_maintenance_mode ||
snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, hintstatus,
HeapTupleHeaderGetXmin(page, tuple) == HeapTupleHeaderGetXmax(page, tuple), buffer, NULL)) {
return false;
}
}
}
} else {
/* xmin is committed, but maybe not according to our snapshot */
if (!HeapTupleHeaderXminFrozen(tuple) &&
!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, buffer)) {
/* tuple xmin has already committed, no need to use xc_maintenance_mod bypass */
if (!GTM_LITE_MODE || snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, XID_COMMITTED,
HeapTupleHeaderGetXmin(page, tuple) == HeapTupleHeaderGetXmax(page, tuple), buffer, NULL)) {
return false; /* treat as still in progress */
}
}
}
recheck_xmax:
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
return true;
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask, tuple->t_infomask2))
return true;
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
TransactionId xmax = HeapTupleHeaderMultiXactGetUpdateXid(page, tuple);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
if (TransactionIdIsCurrentTransactionId(xmax)) {
if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
if (TransactionIdIsInProgress(xmax))
return true;
if (TransactionIdDidCommit(xmax)) {
/* updating transaction committed, but when? */
if (!CommittedXidVisibleInSnapshot(xmax, snapshot, buffer))
return true; /* treat as still in progress */
return false;
}
/* it must have aborted or crashed */
return true;
}
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
bool sync = false;
TransactionId xmax = HeapTupleHeaderGetXmax(page, tuple);
/* IMPORTANT: Version snapshot is independent of the current transaction. */
if (!IsVersionMVCCSnapshot(snapshot) &&
TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot, &hintstatus, buffer, &sync);
/*
* If sync wait, xmax may be modified by others. So we need to check xmax again after acquiring the page lock.
*/
if (sync && (xmax != HeapTupleHeaderGetXmax(page, tuple))) {
goto recheck_xmax;
}
if (hintstatus == XID_COMMITTED) {
/* xmax transaction committed */
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));
}
if (hintstatus == XID_ABORTED) {
if (!LatestFetchCSNDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
snapshot,
"HeapTupleSatisfiesMVCC set HEAP_XMAX_INVALID xid don't abort");
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
if (!visible) {
if (!GTM_LITE_MODE || u_sess->attr.attr_common.xc_maintenance_mode ||
snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple),
snapshot, hintstatus, false, buffer, &sync)) {
if (sync && (xmax != HeapTupleHeaderGetXmax(page, tuple))) {
goto recheck_xmax;
}
return true; /* treat as still in progress */
}
}
} else {
/* xmax is committed, but maybe not according to our snapshot */
if (!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot, buffer)) {
if (!GTM_LITE_MODE || snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple),
snapshot, XID_COMMITTED, false, buffer, NULL)) {
return true; /* treat as still in progress */
}
}
}
return false;
}
四、小结
本次博客对HeapTupleSatisfiesMVCC函数做了详细分析,其用于一般读事务的快照扫描。 对于其实现细节做了详尽分析
下一篇博客我们将关注实现MVCC可见性的其他重点函数HeapTupleSatisfiesVacuum