openGauss源码解析(74)

openGauss源码解析:事务机制源码解析(6)

3. 关键数据结构及函数

1) 快照

快照相关代码如下:

typedef struct SnapshotData {

SnapshotSatisfiesFunc satisfies; /* 判断可见性的函数;通常使用MVCC,即HeapTupleSatisfiesMVCC */

TransactionId xmin; /*当前活跃事务最小值,小于该值的事务说明已结束 */

TransactionId xmax; /*最新提交事务id(latestCompeleteXid)+1,大于等于改值说明事务还未开始,该事务id不可见 */

TransactionId* xip; /*记录当前活跃事务链表,在CSN版本中该值无用 */

TransactionId* subxip; /* 记录缓存子事务活跃链表,在CSN版本中该值无用 */

uint32 xcnt; /* 记录活跃事务的个数(xip中元组数)在CSN版本中该值无用 */

GTM_Timeline timeline; /* openGauss单机中无用 */

uint32 max_xcnt; /* xip的最大个数,CSN版本中该值无用 */

int32 subxcnt; /* 缓存子事务活跃链表的个数,在CSN版本中该值无用 */

int32 maxsubxcnt; /* 缓存子事务活跃链表最大个数,在CSN版本中该值无用 */

bool suboverflowed; /* 子事务活跃链表是否已超过共享内存中预分配的上限,在CSN版本中无用。 */

CommitSeqNo snapshotcsn; /* 快照的CSN号,一般为最新提交事务的CSN号+1(NextCommitSeqNo),CSN号严格小于该值的事务可见。 */

int prepared_array_capacity; /* 单机openGauss无用 */

int prepared_count; /* 单机openGauss无用 */

TransactionId* prepared_array; /* 单机openGauss无用 */

bool takenDuringRecovery; /* 是否Recovery过程中产生的快照 */

bool copied; /* 该快照是会话级别静态的,还是新分配内存拷贝的 */

CommandId curcid; /*事务块中的命令序列号,即同一事务中,前面插入的数据,后面可见。 */

uint32 active_count; /* ActiveSnapshot stack的refcount */

uint32 regd_count; /* RegisteredSnapshotList 的refcount*/

void* user_data; /* 本地多版本快照使用,标记该快照还有线程使用,不能直接释放 */

SnapshotType snapshot_type; /* openGauss单机无用 */

} SnapshotData;

2) HeapTupleSatisfiesMVCC

用于一般读事务的快照扫描,基于CSN的大体逻辑,详细代码如下:

bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)

{

…… /* 初始化变量 */

if (!HeapTupleHeaderXminCommitted(tuple)) { /* 此处先判断用一个bit位记录的hint bit(提示比特位:openGauss判断可见性时,通常需要知道元组xmin和xmax对应的clog的提交状态;为了避免重复访问clog,openGauss内部对可见性判断进行了优化。hint bit是把事务状态直接记录在元组头中,用一个bit位来表示提交和回滚状态。openGauss并不会在事务提交或者回滚时主动更新元组上的 hint bit,而是等到访问该元组并进行可见性判断时,如果发现hint bit没有设置,则从 CLOG 中读取并设置,否则直接读取hint bit的值),防止同一条tuple反复获取事务最终提交状态。如果一次扫描发现该元组的xmin/xmax已经提交,就会打上相应的标记,加速扫描;如果没有标记则继续判断。 */

if (HeapTupleHeaderXminInvalid(tuple)) /* 同样判断hint bit。如果xmin已经标记为invalid说明插入该元组的事务已经回滚,直接返回不可见 */

return false;

if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) { /* 如果是一个事务内部,需要去判断该元组的CID,也即是同一个事务内,后面的查询可以查到当前事务之前插入的扫描结果 */

…….

} else { /* 如果扫描别的事务,需要根据快照判断事务是否可见 */

visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, &hintstatus); /* 通过csnlog判断事务是否可见,并且返回该事务的最终提交状态 */

if (hintstatus == XID_COMMITTED) /* 如果该事务提交,则打上提交的hint bit用于加速判断 */

SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));

if (hintstatus == XID_ABORTED) {

… /* 如果事务回滚,则打上回滚标记 */

SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);

}

if (!visible) { /* 如果xmin不可见,则该元组不可见,否则表示插入该元组的事务对于该次快照已经提交,继续判断删除该元组的事务是否对该次快照提交 */

return false;

}

}

}

} else { /* 如果该条元组的xmin已经被打上提交的hint bit,则通过函数接口CommittedXidVisibleInSnapshot判断是否对本次快照可见 */

/* xmin is committed, but maybe not according to our snapshot */

if (!HeapTupleHeaderXminFrozen(tuple) &&

!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot)) {

return false;

}

}

…… /* 后续xmax的判断同xmin类似,如果xmax对于本次快照可见,则说明删除该条元组的事务已经提交,则不可见,否则可见,此处不再赘述 */

if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {

if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {

if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)

return true; /* 在扫面前该删除事务已经提交 */

else

return false; /* 扫描开始后删除操作的事务才提交 */

}

visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot, &hintstatus);

if (hintstatus == XID_COMMITTED) {

/* 设置xmax的hint bit */

SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));

}

if (hintstatus == XID_ABORTED) {

/* 回滚或者故障 */

SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);

}

if (!visible) {

return true; /* 快照中xmax对应的事务不可见,则认为该元组仍然活跃 */

}

} else {

/* xmax对应的事务已经提交,但是快照中该事务不可见,认为删除该元组的操作未完成,仍然认为该元组可见 */

if (!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot)) {

return true; /* 认为元组可见 */

}

}

return false;

}

3) HeapTupleSatisfiesNow

该函数的逻辑同MVCC类似,只是此时并没有统一快照,而仅仅是判断当前xmin/xmax的状态,而不再继续调用XidVisibleInSnapshot函数、CommittedXidVisibleInSnapshot函数来判断是否对快照可见。

4) HeapTupleSatisfiesVacuum

根据传入的OldestXmin的值返回相应的状态。死亡元组(openGauss多版本机制中不可见的旧版本元组)且没有任何其他未结束的事务可能访问该元组(xmax<oldestXmin),可以被VACUUM清理。本函数具体代码如下:

HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer)

{

…… /* 初始化变量

if (!HeapTupleHeaderXminCommitted(tuple)) { /* hint bit标记加速,与MVCC的逻辑相同。 */

if (HeapTupleHeaderXminInvalid(tuple)) /* 如果xmin未提交,则返回该元组死亡,可以清理。 */

return HEAPTUPLE_DEAD;

xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmin(htup), false); /* 通过CSNLog来获取当前的事务状态 */

if (xidstatus == XID_INPROGRESS) {

if (tuple->t_infomask & HEAP_XMAX_INVALID) /* 如果xmax还没有,说明没有人删除,此时判断该元组正在插入过程中,否则在删除过程中 */

return HEAPTUPLE_INSERT_IN_PROGRESS;

return HEAPTUPLE_DELETE_IN_PROGRESS; /* 返回正在删除的过程中 */

} else if (xidstatus == XID_COMMITTED) { /* 如果xmin提交了,打上hint bit,后面继续看xmax是否提交。 */

SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleGetRawXmin(htup));

} else {

…. /* 事务结束了且未提交,可能是abort或者是crash的事务,一般返回死亡,可删除;单机情形 t_thrd.xact_cxt.useLocalSnapshot没有作用,恒为false。 */

SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);

return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_DEAD : HEAPTUPLE_LIVE);

}

}

/* 接着判断xmax。如果还没有设置xmax说明没有人删除该元组,返回元组存活,不可删除。 */

if (tuple->t_infomask & HEAP_XMAX_INVALID)

return HEAPTUPLE_LIVE;

……

if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) { /*如果xmax提交,则看xmax是否比oldesxmin小。小的话说明没有未结束的事务会访问该元组,可以删除。 */

xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmax(htup), false);

if (xidstatus == XID_INPROGRESS)

return HEAPTUPLE_DELETE_IN_PROGRESS;

else if (xidstatus == XID_COMMITTED)

SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleGetRawXmax(htup));

else {

… /* xmax对应的事务abort或者crash */

SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);

return HEAPTUPLE_LIVE;

}

}

/*判断该元组是否可以删除,xmax<OldestXmin可以删除。 */

if (!TransactionIdPrecedes(HeapTupleGetRawXmax(htup), OldestXmin))

return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_RECENTLY_DEAD : HEAPTUPLE_LIVE);

/* 该元组可以认为已经DEAD,不被任何活跃事务访问,可以删除。 */

return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_DEAD : HEAPTUPLE_LIVE);

}

5) SetXact2CommitInProgress

设置xid对应CSNLog的标记位COMMITSEQNO_COMMIT_INPROGRESS(详情见“5.2.2 事务ID分配及CLOG/CSNLOG”的第2节),表示此xid对应的事务正在提交过程中。该操作是为了保证可见性判断时的原子性,即为了防止并发读事务在CSN号设置的过程中读到不一致的数据。

6) CSNLogSetCommitSeqNo

给对应的xid设置相应的CSNLog。

7) RecordTransactionCommit

记录事务提交,主要是写CLOG、CSNLOG的XLOG日志以及写CLOG及CSNLOG。

posted @ 2024-04-30 09:53  openGauss-bot  阅读(15)  评论(0编辑  收藏  举报