引言
在上一篇博客MVCC(4)–事务可见性中,我们分析了实现判断数据有效性、可见性、可更新性的算法的实现主要函数中的HeapTupleSatisfiesMVCC 本篇博客将会分析主要函数中的另一个—HeapTupleSatisfiesVacuum 文件路径:src\gausskernel\storage\access\heap\heapam_visibility.cpp
Vacuum
一、实现Vacuum的必要性
行存储引擎特性:为了能够充分利用CPU多核的特点,显著加快数据库异常后恢复及备机实例日志回放的速度,行存储引擎采用了多线程并行方式回放日志,如下图所示。
行存储的多版本并发控制机制由于更新和删除并不实际在页面中删除页面本身,数据库长时间运行后,会有大量的历史版本残存在存储空间中,造成了空间的膨胀。为了解决这一问题,存储引擎内部需要定期对历史数据进行清理,以保证数据库的健康运行。行存储对于存储空间的清理存在于多个层面,有多种方式。本博客将要分析的函数是在其中一个关键实现函数——HeapTupleSatisfiesVacuum。
二、Vacuum简介
- Vacuum操作在整个数据库级别清理废旧元组,同时也会清理索引。
- 他可以由数据库用户主动调用,也可以在工作线程满足阈值时或者定期进行调用。
- Vacuum自身除了清理空间以外,也承担了更新统计信息的功能(此处不做展开),让优化器(该部分由队友犇进行分析介绍)能更准确的进行代价优化。
HeapTupleSatisfiesVacuum函数分析
一、简化的HeapTupleSatisfiesVacuum流程图
HeapTupleSatisfiesMVCC`的函数,它用于检查一个堆元组(Heap Tuple)是否满足多版本并发控制(MVCC)的条件。这是数据库事务处理中的一种常见机制,用于处理并发读写操作。
函数接收三个参数,返回一个枚举类型参数
-
htup
,一个堆元组, -
snapshot
,一个快照,通常包含了某一时刻的数据库状态, -
以及
buffer
,一个缓冲区,通常用于存储临时数据或者是待处理的数据 -
返回的枚举类型HTSV_Result定义和个元素含义解释如下
typedef enum {
HEAPTUPLE_DEAD, /* tuple is dead and deletable */
HEAPTUPLE_LIVE, /* tuple is live (committed, no deleter) */
HEAPTUPLE_RECENTLY_DEAD, /* tuple is dead, but not deletable yet */
HEAPTUPLE_INSERT_IN_PROGRESS, /* inserting xact is still in progress */
HEAPTUPLE_DELETE_IN_PROGRESS /* deleting xact is still in progress */
} HTSV_Result;
这里的枚举类型表示了事务可否删除的各种检查结果
它函数于确定元组的VACUUM状态。这个函数的主要目的是检查一个元组是否可能对任何正在运行的事务可见。如果是,那么这个元组还不能被VACUUM移除。这个函数在处理数据库清理和优化操作时非常有用,可以帮助数据库管理系统决定哪些元组可以安全地从数据库中移除。这对于保持数据库性能和管理存储空间非常重要。
其处理过程大致分为如下图所示的四个模块
如上图,绿色方框代表模块,由虚线框住的是该模块的下子模块。
- 函数首先对变量进行初始化,然后依据opengauss自定义的本地debug函数选择是否显示信息。
- 然后函数进入事务未提交条件下的处理:这里我将其划分为了四种情况的处理:1.Xmin可见性,子事务处理,XID状态检查,其他状态检查。
- 最后进入对Xmax的检查处理,进入这个模块的时候表示事务已经提交了。我将这个模块的检查也划分为三个小模块的检查:只被锁检查,多事务标记检查,Xmax提交检查。
函数的所有检查处理都是建立在获取元组的头部信息的基础上的,它会根据元组的不同状态返回不同的结果,例如:如果元组被标记为无效或者正在进行插入操作,则返回相应的状态。如果元组被其他事务锁定,则返回”活动”状态。最后,如果元组没有被锁定或者无效,则返回”死亡”状态。
以上就是元组删除判断的简化流程,接下来我们将对各个子模块进行详细分析。
二、源码以及完整注释分析
变量初始化以及debug信息
HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer, bool isAnalyzing)
{
HeapTupleHeader tuple = htup->t_data;
TransactionIdStatus xidstatus;
Assert(ItemPointerIsValid(&htup->t_self));
Assert(htup->t_tableOid != InvalidOid);
Page page = BufferGetPage(buffer);
HeapTupleCopyBaseFromPage(htup, page);
if (SHOW_DEBUG_MESSAGE()) {
ereport(DEBUG1,
(errmsg("HeapTupleSatisfiesVacuum self(%d,%d) ctid(%d,%d) cur_xid %lu xmin %ld"
" xmax %ld OldestXmin %ld",
ItemPointerGetBlockNumber(&htup->t_self),
ItemPointerGetOffsetNumber(&htup->t_self),
ItemPointerGetBlockNumber(&tuple->t_ctid),
ItemPointerGetOffsetNumber(&tuple->t_ctid),
GetCurrentTransactionIdIfAny(),
HeapTupleHeaderGetXmin(page, tuple),
HeapTupleHeaderGetXmax(page, tuple),
OldestXmin)));
}
函数头初始化变量,并且在log_min_messages<DEBUG1
(也就是14)的时候返回DEBUG信息
事务未提交的检查代码块
if (!HeapTupleHeaderXminCommitted(tuple)) {
if (HeapTupleHeaderXminInvalid(tuple))
return HEAPTUPLE_DEAD;
xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmin(htup));
if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmin(htup))) {
if (tuple->t_infomask & HEAP_XMAX_INVALID)
return HEAPTUPLE_INSERT_IN_PROGRESS;
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask, tuple->t_infomask2))
return HEAPTUPLE_INSERT_IN_PROGRESS;
if (TransactionIdIsCurrentTransactionId(HeapTupleGetRawXmax(htup))) {
return HEAPTUPLE_DELETE_IN_PROGRESS;
}
return HEAPTUPLE_INSERT_IN_PROGRESS;
} else if (xidstatus == XID_INPROGRESS && TransactionIdIsInProgress(HeapTupleGetRawXmin(htup))) {
return HEAPTUPLE_INSERT_IN_PROGRESS;
} else if (xidstatus == XID_COMMITTED ||
(xidstatus == XID_INPROGRESS && TransactionIdDidCommit(HeapTupleGetRawXmin(htup)))) {
if (!isAnalyzing) {
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleGetRawXmin(htup));
}
} else {
if (u_sess->attr.attr_storage.enable_debug_vacuum && t_thrd.utils_cxt.pRelatedRel) {
elogVacuumInfo(
t_thrd.utils_cxt.pRelatedRel, htup, "HeapTupleSatisfiedVacuum set HEAP_XMIN_INVALID", OldestXmin);
}
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
NULL,
"HeapTupleSatisfiedVacuum set HEAP_XMIN_INVALID xid don't abort");
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_DEAD : HEAPTUPLE_LIVE);
}
}
上面的代码块流程如下图所示
其中文末的Hintbit设置有着防止资源浪费的作用,具体原理解释如下: 判断用1bit记录的hint bit,防止同一条tuple反复获取事务最终提交状态,如果一次扫描判断发现该元组的xmin/xmax已经提交,就会打上相应的标记,加速扫描;如果没有标记则继续判断。
事务已提交的XMAX检查代码块
接上面流程图黄色方框,表示事务已经被提交,现在进行接下来的XMAX检查步骤
如果掩码信息给出xmax无效,表示事务仍在活跃中,即没有更新,也没有被删除
if (tuple->t_infomask & HEAP_XMAX_INVALID)
return HEAPTUPLE_LIVE;
进入只被锁定的检查,表示事务仍在活跃中,即没有更新,也没有被删除。如果这个标志HEAP_XMAX_IS_LOCKED_ONLY
被设置,那么就意味着这个元组已经被锁定,但是并没有被更新。因此,无论如何,这个元组都是活动的(即,它没有被删除或更新)。然而,我们应该确保一旦事务结束,XMAX_COMMITTED
或者XMAX_INVALID
中的一个应该被设置,以减少未来事务检查元组的成本。同时,标记死亡的MultiXacts为无效。
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask, tuple->t_infomask2)) {
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup)))
return HEAPTUPLE_LIVE;
} else {
xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmax(htup));
if (xidstatus == XID_INPROGRESS && TransactionIdIsInProgress(HeapTupleGetRawXmax(htup))) {
return HEAPTUPLE_LIVE;
}
}
if (u_sess->attr.attr_storage.enable_debug_vacuum && t_thrd.utils_cxt.pRelatedRel) {
elogVacuumInfo(
t_thrd.utils_cxt.pRelatedRel, htup, "HeapTupleSatisfiedVacuum set HEAP_XMAX_INVALID ", OldestXmin);
}
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
return HEAPTUPLE_LIVE;
}
代码块结尾同理使用hint bit判断,如果xmin已经标记为invalid说明插入该元组的事务已经回滚,返回不可见,即是死亡。
接下来的代码块检查了元组是否被多个事务更新(HEAP_XMAX_IS_MULTI
),以及事务的状态。如果事务仍在进行中,那么元组仍然是活动的。
最后,无论xmax事务是提交、中止还是崩溃,我们都不真正关心。我们知道xmax锁定了元组,但是它并没有也永远不会实际更新它。因此,我们设置了HEAP_XMAX_INVALID
标志,并返回’HEAPTUPLE_LIVE’表示元组是活动的。
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
TransactionId xmax = HeapTupleHeaderGetUpdateXid(page, tuple);
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask, tuple->t_infomask2));
Assert(TransactionIdIsValid(xmax));
if (TransactionIdIsInProgress(xmax)) {
return HEAPTUPLE_DELETE_IN_PROGRESS;
} else if (TransactionIdDidCommit(xmax)) {
if (!TransactionIdPrecedes(xmax, OldestXmin))
return HEAPTUPLE_RECENTLY_DEAD;
return HEAPTUPLE_DEAD;
} else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(page, tuple))) {
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
return HEAPTUPLE_LIVE;
}
后面这段代码块
-
使用
TransactionIdGetStatus
函数获取xmax
的状态。如果状态是XID_INPROGRESS
并且事务仍在进行中,那么返回HEAPTUPLE_DELETE_IN_PROGRESS
。 -
如果
xmax
的状态是XID_COMMITTED
或者事务已经提交,那么设置元组的HEAP_XMAX_COMMITTED
标志。 -
如果
xmax
不在进行中,也没有提交,那么可能是已经中止或者崩溃。这时会检查是否已经中止,如果没有,那么会抛出错误。然后设置元组的HEAP_XMAX_INVALID
标志,并返回HEAPTUPLE_LIVE
。 -
在所有这些检查之后,
xmax
已经提交,但可能还没有设置提示位。所以不能再断言提示位已经设置。
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmax(htup));
if (xidstatus == XID_INPROGRESS && TransactionIdIsInProgress(HeapTupleGetRawXmax(htup))) {
return HEAPTUPLE_DELETE_IN_PROGRESS;
} else if (xidstatus == XID_COMMITTED ||
(xidstatus == XID_INPROGRESS && TransactionIdDidCommit(HeapTupleGetRawXmax(htup)))) {
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleGetRawXmax(htup));
} else {
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
NULL,
"HeapTupleSatisfiedVacuum set HEAP_XMAX_INVALID xid don't abort");
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return HEAPTUPLE_LIVE;
}
}
最后检查已经提交删除的元组是否还可见,因为如果删除提交时间不够充分,会仍有一些事务能够看见该元组。
if (!TransactionIdPrecedes(HeapTupleGetRawXmax(htup), OldestXmin))
return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_RECENTLY_DEAD : HEAPTUPLE_LIVE);
return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_DEAD : HEAPTUPLE_LIVE);
三、小结
本次博客对HeapTupleSatisfiesVacuum函数做了详细分析,其用于检验元组可否删除元组
下一篇博客我们将关注实现MVCC可见性的其他重点函数SetXact2CommitInProgress、CSNLogSetCommitSeqNo