PostgreSQL 14在MVCC上面有所增强,以保证在大批量客户端连接场景下性能不会出现显著下降(详细可参见参考资料中的链接)。本节介绍了提交记录“Don’t compute global horizons while building snapshots.”中heapam_visibility.c中的修改以及涉及的相关数据结构。
一、数据结构
HTSV_Result
/* Result codes for HeapTupleSatisfiesVacuum */
/* HeapTupleSatisfiesVacuum返回的结果码 */
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;
HTSV == HeapTupleSatisfiesVacuum首字母大写
二、源码解读
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -1154,19 +1154,56 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
* we mainly want to know is if a tuple is potentially visible to *any*
* running transaction. If so, it can't be removed yet by VACUUM.
*
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()). Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
+ * OldestXmin is a cutoff XID (obtained from
+ * GetOldestNonRemovableTransactionId()). Tuples deleted by XIDs >=
+ * OldestXmin are deemed "recently dead"; they might still be visible to some
+ * open transaction, so we can't remove them, even if we see that the deleting
+ * transaction has committed.
*/
/*
设xID = 从GetOldestNonRemovableTransactionId
对于所有 < xID 的事务IDs,可被VACUUM清除
HeapTupleSatisfiesVacuum函数,
调用HeapTupleSatisfiesVacuumHorizon判断元组是否“最近死亡-HEAPTUPLE_RECENTLY_DEAD”,
*/
HTSV_Result
HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
Buffer buffer)
+{
+ TransactionId dead_after = InvalidTransactionId;
+ HTSV_Result res;
+
+ res = HeapTupleSatisfiesVacuumHorizon(htup, buffer, &dead_after);
+
+ if (res == HEAPTUPLE_RECENTLY_DEAD)
+ {
+ Assert(TransactionIdIsValid(dead_after));
+
+ if (TransactionIdPrecedes(dead_after, OldestXmin))
+ res = HEAPTUPLE_DEAD;
+ }
+ else
+ Assert(!TransactionIdIsValid(dead_after));
+
+ return res;
+}
+
+/*
+ * Work horse for HeapTupleSatisfiesVacuum and similar routines.
+ *
+ * In contrast to HeapTupleSatisfiesVacuum this routine, when encountering a
+ * tuple that could still be visible to some backend, stores the xid that
+ * needs to be compared with the horizon in *dead_after, and returns
+ * HEAPTUPLE_RECENTLY_DEAD. The caller then can perform the comparison with
+ * the horizon. This is e.g. useful when comparing with different horizons.
+ *
+ * Note: HEAPTUPLE_DEAD can still be returned here, e.g. if the inserting
+ * transaction aborted.
+ */
+HTSV_Result
+HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *dead_after)
{
HeapTupleHeader tuple = htup->t_data;
Assert(ItemPointerIsValid(&htup->t_self));
Assert(htup->t_tableOid != InvalidOid);
+ Assert(dead_after != NULL);
+
+ *dead_after = InvalidTransactionId;
/*
* Has inserting transaction committed?
@@ -1323,17 +1360,15 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
else if (TransactionIdDidCommit(xmax))//xmax事务已提交,说明元组已被删除
{
/*
- * The multixact might still be running due to lockers. If the
- * updater is below the xid horizon, we have to return DEAD
- * regardless -- otherwise we could end up with a tuple where the
- * updater has to be removed due to the horizon, but is not pruned
- * away. It's not a problem to prune that tuple, because any
- * remaining lockers will also be present in newer tuple versions.
+ * The multixact might still be running due to lockers. Need to
+ * allow for pruning if below the xid horizon regardless --
+ * otherwise we could end up with a tuple where the updater has to
+ * be removed due to the horizon, but is not pruned away. It's
+ * not a problem to prune that tuple, because any remaining
+ * lockers will also be present in newer tuple versions.
*/
- if (!TransactionIdPrecedes(xmax, OldestXmin))
- return HEAPTUPLE_RECENTLY_DEAD;
-
- return HEAPTUPLE_DEAD;
+ *dead_after = xmax;//返回xmax
+ return HEAPTUPLE_RECENTLY_DEAD;//状态:最近死亡
}
else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
{
@@ -1372,14 +1407,11 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
}
/*
- * Deleter committed, but perhaps it was recent enough that some open
- * transactions could still see the tuple.
+ * Deleter committed, allow caller to check if it was recent enough that
+ * some open transactions could still see the tuple.
删除事务已提交,但仍可能被其他活动事务可见
*/
- if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
- return HEAPTUPLE_RECENTLY_DEAD;
-
- /* Otherwise, it's dead and removable */
- return HEAPTUPLE_DEAD;
+ *dead_after = HeapTupleHeaderGetRawXmax(tuple);//xmax
+ return HEAPTUPLE_RECENTLY_DEAD;//状态:最近死亡
}
@@ -1393,14 +1425,28 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
*
* This is an interface to HeapTupleSatisfiesVacuum that's callable via
* HeapTupleSatisfiesSnapshot, so it can be used through a Snapshot.
- * snapshot->xmin must have been set up with the xmin horizon to use.
+ * snapshot->vistest must have been set up with the horizon to use.
snapshot->vistest必须设置
*/
static bool
HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
Buffer buffer)
{
- return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)//原逻辑:调用HeapTupleSatisfiesVacuum判断
- != HEAPTUPLE_DEAD;
+ TransactionId dead_after = InvalidTransactionId;
+ HTSV_Result res;
+
+ res = HeapTupleSatisfiesVacuumHorizon(htup, buffer, &dead_after);
//新逻辑:调用HeapTupleSatisfiesVacuumHorizon获取是否最近死亡,
// 若是,则进一步调用GlobalVisTestIsRemovableXid判断是否可被清除
+
+ if (res == HEAPTUPLE_RECENTLY_DEAD)//元组最近死亡
+ {
+ Assert(TransactionIdIsValid(dead_after));
+
+ if (GlobalVisTestIsRemovableXid(snapshot->vistest, dead_after))//可见性判断
+ res = HEAPTUPLE_DEAD;//可以被清除
+ }
+ else
+ Assert(!TransactionIdIsValid(dead_after));
+
+ return res != HEAPTUPLE_DEAD;//HEAPTUPLE_DEAD状态的元组才能被清除
}
@@ -1418,7 +1464,7 @@ HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
* if the tuple is removable.
*/
bool
-HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
+HeapTupleIsSurelyDead(HeapTuple htup, GlobalVisState *vistest)
{
HeapTupleHeader tuple = htup->t_data;
@@ -1459,7 +1505,8 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
return false;
/* Deleter committed, so tuple is dead if the XID is old enough. */
- return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);//原逻辑:元组xmax比OldestXmin要小则可清除,否则不能清除
+ return GlobalVisTestIsRemovableXid(vistest,
+ HeapTupleHeaderGetRawXmax(tuple));//新逻辑:调用GlobalVisTestXXX函数,确认元组是否可清除
}
/*
GlobalVisTestXXX函数在procarray.c文件中,后续再行介绍。
三、跟踪分析
N/A
四、参考资料
1.
Improving connection scalability: GetSnapshotData()
2.
snapshot scalability: Don’t compute global horizons while building snapshots.