PostgreSQL 14在MVCC上面有所增强,以保证在大批量客户端连接场景下性能不会出现显著下降(详细可参见参考资料中的链接)。本节介绍了提交记录“Don’t compute global horizons while building snapshots.”中heapam_handler.c中的修改以及涉及的相关数据结构。
一、数据结构
SnapshotData
表示所有类型的快照结构体
/*
* The different snapshot types. We use SnapshotData structures to represent
* both "regular" (MVCC) snapshots and "special" snapshots that have non-MVCC
* semantics. The specific semantics of a snapshot are encoded by its type.
* 快照类型。
* 使用SnapshotData结构体表示常规的(MVCC)的快照和特别的快照(非MVCC语义)。
* 快照的特殊语义通过其类型进行编码。
*
* The behaviour of each type of snapshot should be documented alongside its
* enum value, best in terms that are not specific to an individual table AM.
* 每种快照类型的行为与其enum值一起记录而不是特定于单个表的AM。
*
* The reason the snapshot type rather than a callback as it used to be is
* that that allows to use the same snapshot for different table AMs without
* having one callback per AM.
* 使用快照类型而不是回调函数的原因是对于不同的表AMs(访问方法)可使用相同的快照而不是每个AM使用一个回调函数
*/
typedef enum SnapshotType
{
/*-------------------------------------------------------------------------
* A tuple is visible iff the tuple is valid for the given MVCC snapshot.
*
* Here, we consider the effects of:
* - all transactions committed as of the time of the given snapshot
* - previous commands of this transaction
*
* Does _not_ include:
* - transactions shown as in-progress by the snapshot
* - transactions started after the snapshot was taken
* - changes made by the current command
* 元组对于给定的MVCC快照是有效的,则该元组可见
* 考虑:
* 给定快照时所有提交的事务
* 该事务先前执行的命令
* 不考虑:
* 正在处理中的事务
* 快照点之后的事务
* 当前命令做出的修改
* -------------------------------------------------------------------------
*/
SNAPSHOT_MVCC = 0,//常规的MVCC
/*-------------------------------------------------------------------------
* A tuple is visible iff the tuple is valid "for itself".
* 对于“自身”是有效,则元组可见
*
* Here, we consider the effects of:
* - all committed transactions (as of the current instant)
* - previous commands of this transaction
* - changes made by the current command
*
* Does _not_ include:
* - in-progress transactions (as of the current instant)
* 考虑:
* 所有已提交的事务(当前实例)
* 本事务先前的命令
* 当前命令的修改
* 不考虑
* 正在进行中的事务
* -------------------------------------------------------------------------
*/
SNAPSHOT_SELF,
/*
* Any tuple is visible.
* 所有元组都是可见的
*/
SNAPSHOT_ANY,
/*
* A tuple is visible iff the tuple is valid as a TOAST row.
* 元组作为TOAST行为有效时,元组是可见的。
*/
SNAPSHOT_TOAST,
/*-------------------------------------------------------------------------
* A tuple is visible iff the tuple is valid including effects of open
* transactions.
* 只要元组有效(包括开放事务的影响)则元组可见
*
* Here, we consider the effects of:
* - all committed and in-progress transactions (as of the current instant)
* - previous commands of this transaction
* - changes made by the current command
* 这里,考虑:
* 所有已提交和处理中的事务
* 该事务先前的命令
* 当前命令作出的修改
*
* This is essentially like SNAPSHOT_SELF as far as effects of the current
* transaction and committed/aborted xacts are concerned. However, it
* also includes the effects of other xacts still in progress.
* 就当前和提交/中止事务的影响来看,本质上类似于SNAPSHOT_SELF。
*
* A special hack is that when a snapshot of this type is used to
* determine tuple visibility, the passed-in snapshot struct is used as an
* output argument to return the xids of concurrent xacts that affected
* the tuple. snapshot->xmin is set to the tuple's xmin if that is
* another transaction that's still in progress; or to
* InvalidTransactionId if the tuple's xmin is committed good, committed
* dead, or my own xact. Similarly for snapshot->xmax and the tuple's
* xmax. If the tuple was inserted speculatively, meaning that the
* inserter might still back down on the insertion without aborting the
* whole transaction, the associated token is also returned in
* snapshot->speculativeToken. See also InitDirtySnapshot().
* 这种快照用于确定元组的可见性,传入的快照结构体用作输出参数返回当前影响元组的事务xids。
* snapshot->xmin设置为元组的xmin,如果这是另外一个进行中的事务;
* 设置为InvalidTransactionId如果元组的xmin对应的事务已完成提交/终止/或者是本事务
* snapshot->xmax与元组xmax与此类似。
* 如果tuple是speculatively插入的,这意味着插入事务仍可以继续插入,而不会中止整个事务,
* 相关的令牌会通过snapshot—>speculativeToken返回。
* -------------------------------------------------------------------------
*/
SNAPSHOT_DIRTY,
/*
* A tuple is visible iff it follows the rules of SNAPSHOT_MVCC, but
* supports being called in timetravel context (for decoding catalog
* contents in the context of logical decoding).
* 如遵循SNAPSHOT_MVCC规则,则元组是可见的,但支持在timetravel上下文中调用
* (在逻辑解码上下文时解码catalog内容)
*/
SNAPSHOT_HISTORIC_MVCC,
/*
* A tuple is visible iff the tuple might be visible to some transaction;
* false if it's surely dead to everyone, i.e., vacuumable.
*
* For visibility checks snapshot->min must have been set up with the xmin
* horizon to use.
* 对于某些事务是可见的,则元组可见。
* 如已确认“死亡”则为False,如需要被清理。
* 对于可见性检查而言,snapshot->min必须使用xmin来配置
*/
SNAPSHOT_NON_VACUUMABLE
} SnapshotType;
typedef struct SnapshotData *Snapshot;
#define InvalidSnapshot ((Snapshot) NULL)
/*
* Struct representing all kind of possible snapshots.
* 对应所有可能快照的结构体
*
* There are several different kinds of snapshots:
* * Normal MVCC snapshots
* * MVCC snapshots taken during recovery (in Hot-Standby mode)
* * Historic MVCC snapshots used during logical decoding
* * snapshots passed to HeapTupleSatisfiesDirty()
* * snapshots passed to HeapTupleSatisfiesNonVacuumable()
* * snapshots used for SatisfiesAny, Toast, Self where no members are
* accessed.
* 快照类型包括(详见SnapshotType):
* 普通的MVCC快照
* 恢复期间的MVCC快照
* 逻辑解码时的历史MVCC快照
* 传递给HeapTupleSatisfiesDirty的快照
* 传递给HeapTupleSatisfiesNonVacuumable的快照
* 传递给SatisfiesAny,Toast,Self的快照
*
* TODO: It's probably a good idea to split this struct using a NodeTag
* similar to how parser and executor nodes are handled, with one type for
* each different kind of snapshot to avoid overloading the meaning of
* individual fields.
* TODO:通过NodeTag来拆分结构体
*/
typedef struct SnapshotData
{
//快照类型
SnapshotType snapshot_type; /* type of snapshot */
/*
* The remaining fields are used only for MVCC snapshots, and are normally
* just zeroes in special snapshots. (But xmin and xmax are used
* specially by HeapTupleSatisfiesDirty, and xmin is used specially by
* HeapTupleSatisfiesNonVacuumable.)
* 余下字段用于MVCC快照,其他特殊的快照设置为0.
* (xmin和xmax用于HeapTupleSatisfiesDirty,xmin用于HeapTupleSatisfiesNonVacuumable)
*
* An MVCC snapshot can never see the effects of XIDs >= xmax. It can see
* the effects of all older XIDs except those listed in the snapshot. xmin
* is stored as an optimization to avoid needing to search the XID arrays
* for most tuples.
* MVCC快照用于都看不到XIDs >= xmax的影响,但可以看到除了快照列表中的事务影响。
* xmin作为优化的手段用于避免检索XID数组
*/
TransactionId xmin; /* all XID < xmin are visible to me */
TransactionId xmax; /* all XID >= xmax are invisible to me */
/*
* For normal MVCC snapshot this contains the all xact IDs that are in
* progress, unless the snapshot was taken during recovery in which case
* it's empty. For historic MVCC snapshots, the meaning is inverted, i.e.
* it contains *committed* transactions between xmin and xmax.
* xip用于存放所有正在进行中的事务,除了在恢复期间的快照。
* 对于历史MVCC快照,意思的相反的,也就是说xip是xmin和xmax之间已提交的事务
*
* note: all ids in xip[] satisfy xmin <= xip[i] < xmax
* xip数组中的元素满足:xmin <= xip[i] < xmax
*/
TransactionId *xip;
uint32 xcnt; /* 数组大小,# of xact ids in xip[] */
/*
* For non-historic MVCC snapshots, this contains subxact IDs that are in
* progress (and other transactions that are in progress if taken during
* recovery). For historic snapshot it contains *all* xids assigned to the
* replayed transaction, including the toplevel xid.
* 对于非历史MVCC快照,包含正在进行中的子事务IDs。
* 对于历史MVCC快照,包含所有分配给已回放事务的所有xids(包括顶层xid)
*
* note: all ids in subxip[] are >= xmin, but we don't bother filtering
* out any that are >= xmax
*/
TransactionId *subxip;
int32 subxcnt; /* 数组大小,# of xact ids in subxip[] */
bool suboverflowed; /* 数组溢出?has the subxip array overflowed? */
bool takenDuringRecovery; /* recovery-shaped snapshot? */
bool copied; /* false if it's a static snapshot */
CommandId curcid; /* 在本事务中,CID < curcid则可见,in my xact, CID < curcid are visible */
/*
* An extra return value for HeapTupleSatisfiesDirty, not used in MVCC
* snapshots.
* HeapTupleSatisfiesDirty额外的返回值
*/
uint32 speculativeToken;
/*
* For SNAPSHOT_NON_VACUUMABLE (and hopefully more in the future) this is
* used to determine whether row could be vacuumed.
* 对于SNAPSHOT_NON_VACUUMABLE,用于确定哪些行可以被清理
*/
struct GlobalVisState *vistest;
/*
* Book-keeping information, used by the snapshot manager
* 用于快照管理器的信息
*/
uint32 active_count; /* refcount on ActiveSnapshot stack */
uint32 regd_count; /* refcount on RegisteredSnapshots */
pairingheap_node ph_node; /* link in the RegisteredSnapshots heap */
TimestampTz whenTaken; /* timestamp when snapshot was taken */
XLogRecPtr lsn; /* position in the WAL stream when taken */
/*
* The transaction completion count at the time GetSnapshotData() built
* this snapshot. Allows to avoid re-computing static snapshots when no
* transactions completed since the last GetSnapshotData().
*/
uint64 snapXactCompletionCount;
} SnapshotData;
二、源码解读
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1203,7 +1203,7 @@ heapam_index_build_range_scan(Relation heapRelation,
/* okay to ignore lazy VACUUMs here */
if (!IsBootstrapProcessingMode() && !indexInfo->ii_Concurrent)
/*
在常规的创建索引时,由于必须检索所有的元组并执行时间表达式检查,因此必须使用SnapshotAny
而,使用SnapshotAny(所有元组均可见),必须调用GetOldestXmin,改为调用GetOldestNonRemovableTransactionId
在bootstrap或者同步创建索引时,则使用常规的MVCC快照
*/
- OldestXmin = GetOldestXmin(heapRelation, PROCARRAY_FLAGS_VACUUM);
+ OldestXmin = GetOldestNonRemovableTransactionId(heapRelation);
if (!scan)
{
@@ -1244,6 +1244,17 @@ heapam_index_build_range_scan(Relation heapRelation,
hscan = (HeapScanDesc) scan;
+ /*
+ * Must have called GetOldestNonRemovableTransactionId() if using
+ * SnapshotAny. Shouldn't have for an MVCC snapshot. (It's especially
+ * worth checking this for parallel builds, since ambuild routines that
+ * support parallel builds must work these details out for themselves.)
+ */
+ Assert(snapshot == SnapshotAny || IsMVCCSnapshot(snapshot));
+ Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
+ !TransactionIdIsValid(OldestXmin));
+ Assert(snapshot == SnapshotAny || !anyvisible);
+
/* Publish number of blocks to scan */
if (progress)
{
@@ -1263,17 +1274,6 @@ heapam_index_build_range_scan(Relation heapRelation,
nblocks);
}
/*
这部分逻辑往上移
*/
- /*
- * Must call GetOldestXmin() with SnapshotAny. Should never call
- * GetOldestXmin() with MVCC snapshot. (It's especially worth checking
- * this for parallel builds, since ambuild routines that support parallel
- * builds must work these details out for themselves.)
- */
- Assert(snapshot == SnapshotAny || IsMVCCSnapshot(snapshot));
- Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
- !TransactionIdIsValid(OldestXmin));
- Assert(snapshot == SnapshotAny || !anyvisible);
-
/* set our scan endpoints */
if (!allow_sync)
heap_setscanlimits(scan, start_blockno, numblocks);
三、跟踪分析
N/A
四、参考资料
1.
Improving connection scalability: GetSnapshotData()
2.
snapshot scalability: Don’t compute global horizons while building snapshots.