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。