海山数据库(He3DB)源码详解:He3DB-CLOG日志管理器函数之TransactionIdSetTreeStatus
1 背景
本文主要针对海山数据库中CLOG日志管理器部分关键函数源码进行研读。主要涉及TransactionIdSetTreeStatus源码解析,以及该函数的调用栈
2 TransactionIdSetTreeStatus函数源码解析
void
TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status, XLogRecPtr lsn)
{}
xid父事务idnsubxids子事务个数,为subxids数组的个数subxids指向子事务id数组的指针status状态lsn该状态在WAL中的位置
int pageno = TransactionIdToPage(xid);
获取父事务ID对应的CLOG页面号pageno
Assert(status == TRANSACTION_STATUS_COMMITTED || status == TRANSACTION_STATUS_ABORTED);
确保传入的状态只能是已提交或者已中止(因为CLOG是只记录事务的结果状态)
for (i = 0; i < nsubxids; i++)
{
if (TransactionIdToPage(subxids[i]) != pageno)
break;
}
- 循环遍历子事务(
nsubxids表示子事务数量) - 判断哪些子事务是否和父事务在同一页面上
-
- 若不在同一页面,则跳出循环,记录
i的值
- 若不在同一页面,则跳出循环,记录
-
- 若都在同一页面,则循环结束后
i的值与nsubxid相等
- 若都在同一页面,则循环结束后
注意: 这里遍历的是总子事务个数,nsubxids该数量也即是subxids数组中元素的数量 ,i也是这个含义,表示子事务个数
因此,当跳出该for循环后,得到的i,表示不在编号为pageno页面(即要更新状态的第一页,下文简称第一页)的子事务索引(subxids数组中下标为i)
if (i == nsubxids)
{
TransactionIdSetPageStatus(xid, nsubxids, subxids, status, lsn,
pageno, true);
}
else
{}
如果所有子事务都和父事务在同一页面上,则一次性更新父事务和所有子事务的状态
如果并不是子事务都和父事务在同一页面上,则:
else
{
...
}
int nsubxids_on_first_page = i;
i的值赋给nsubxids_on_first_page
if (status == TRANSACTION_STATUS_COMMITTED)
set_status_by_pages(nsubxids - nsubxids_on_first_page,
subxids + nsubxids_on_first_page,
TRANSACTION_STATUS_SUB_COMMITTED, lsn);
注意: 涉及到的是 多页CLOG
在处理多页提交日志时,通常第一页会被优先处理或已经以某种方式被标记为开始处理,因此不需要再次标记为子提交
为了避免对第一页进行不必要的重复操作,建议我们跳过对第一页上子事务ID(subxids)的“子提交”标记
- 如果状态为已提交
set_status_by_pages(nsubxids - nsubxids_on_first_page, subxids + nsubxids_on_first_page, TRANSACTION_STATUS_SUB_COMMITTED, lsn);
参数:
nsubxids - nsubxids_on_first_page:不在第一页上的子事务ID的数量subxids + nsubxids_on_first_page:作为指向这些不在第一页上的子事务ID数组的指针
-
- 先将不在第一页的子事务标记为子事务已提交状态
TRANSACTION_STATUS_SUB_COMMITTED,即 跳过了第一页 上的子事务,因为它们将在下一步中更新
- 先将不在第一页的子事务标记为子事务已提交状态
pageno = TransactionIdToPage(xid);
TransactionIdSetPageStatus(xid, nsubxids_on_first_page, subxids, status,
lsn, pageno, false);
- 获取父事务id对应的页面号
- 更新第一页子事务ID状态
set_status_by_pages(nsubxids - nsubxids_on_first_page, subxids + nsubxids_on_first_page, status, lsn); }
- 再调用一次,更新之前已经标记为子提交状态的子事务ID(即那些不在第一页上的)的状态更新为完全提交状态
【这个调用实际上是对之前子提交状态的一个最终确认和更新】
这段代码的目的是在处理跨越多个CLOG事务提交时:
先将非第一页的子事务ID标记为子提交状态
然后更新父事务ID及其在第一页上的子事务ID的状态
最后再将之前标记为子提交状态的子事务ID更新为完全提交状态。这样做是为了确保在并发环境下事务状态的一致性和正确性。
3 函数调用栈
- 第一层调用栈如图所示

TransactionIdSetTreeStatus
+--> set_status_by_pages
|
+--> TransactionIdSetPageStatus
|
+--> TransactionIdToPage
其中TransactionIdToPage为宏,作用是将给定的事务 ID(xid)转换为对应的页面编号,具体实现如下所示
#define TransactionIdToPage(xid) ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
- 第二层调用栈
set_status_by_pages如图所示

set_status_by_pages
+--> TransactionIdSetPageStatus
|
+--> TransactionIdToPage
这里仅对涉及到的宏进行讲解,具体函数作用将在后续文章中进一步解析
GetLSNIndex宏,作用是用于计算一个LSN索引值,具体实现如下所示
#define GetLSNIndex(slotno, xid) ((slotno) * CLOG_LSNS_PER_PAGE + \
((xid) % (TransactionId) CLOG_XACTS_PER_PAGE) / CLOG_XACTS_PER_LSN_GROUP)
TransactionIdToByte宏,作用是用于将事务 ID 转换为与字节相关的索引,具体实现如下所示
#define TransactionIdToByte(xid) (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
TransactionIdToBIndex宏,这个宏用于获取事务 ID 在字节范围内的索引。
通过对CLOG_XACTS_PER_BYTE取模得到事务 ID 在字节范围内的相对位置,具体实现如下所示
#define TransactionIdToBIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)
TransactionIdToPgIndex宏
这个宏用于获取事务 ID 在页范围内的索引。
通过对CLOG_XACTS_PER_PAGE取模得到事务 ID 在一页中的位置,具体实现过程如下所示:
#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
- 第二层调用栈
TransactionIdSetPageStatus如图所示
static void
TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
XLogRecPtr lsn, int pageno,
bool all_xact_same_page)
{}

TransactionIdSetPageStatus
+--> TransactionGroupUpdateXidStatus
|
+--> TransactionIdSetPageStatusInternal
涉及到的宏函数与set_status_by_pages一致,不做赘述
4 总结
TransactionIdSetTreeStatus这个函数的主要作用是记录事务及其子事务树在提交日志中的最终状态,并且在该函数的调用栈关系中,TransactionIdSetPageStatus和set_status_by_pages是第一层函数调用,其核心讲解内容将在后续进行。




