海山数据库(He3DB)源码详解:RecordTransactionCommit 函数解读

7 阅读4分钟

海山数据库(He3DB)源码详解:RecordTransactionCommit 函数解读

RecordTransactionCommit 是 PostgreSQL 中用于记录事务提交的关键函数。它负责在事务结束时执行一系列操作,包括日志记录、清理资源、更新事务状态等。以下是对这个函数的逐行解释:

1. 函数目标

RecordTransactionCommit 的主要目标是:

  1. 记录事务提交的 XLOG(Write-Ahead Logging)记录。
  2. 清理事务相关的资源。
  3. 更新事务状态(如 CLOG)。
  4. 确保在同步提交模式下,所有必要的日志记录都被写入磁盘。

2. 函数逻辑

  1. 获取事务 ID
  • 如果事务有有效的 XID,进入提交记录的准备阶段。
  • 如果没有有效的 XID,检查是否有待删除的文件或统计信息。
  1. 检查待删除的文件或统计信息
  • 如果有,抛出错误(不能提交删除文件但没有 XID 的事务)。
  • 如果没有,检查是否有无效化消息。
  1. 检查无效化消息
  • 如果有无效化消息,记录这些消息。
  • 如果没有,直接结束(不写入 XLOG)。
  1. 准备提交记录
  • 记录逻辑解码相关的无效化信息。
  • 写入提交记录。
  1. 检查提交模式
  • 如果是同步提交,刷新 XLOG 并更新 CLOG,等待同步复制完成。
  • 如果是异步提交,设置异步提交的 LSN,不立即刷新 XLOG。
  1. 清理和退出
  • 释放分配的资源,结束函数执行。

以下是函数执行的流程图:

image-1.png

3. 源码解析

1. 初始化变量

TransactionId xid = GetTopTransactionIdIfAny();
bool markXidCommitted = TransactionIdIsValid(xid);
TransactionId latestXid = InvalidTransactionId;
int nrels = 0;
RelFileNode *rels = NULL;
int nchildren = 0;
TransactionId *children = NULL;
int ndroppedstats = 0;
xl_xact_stats_item *droppedstats = NULL;
int nmsgs = 0;
SharedInvalidationMessage *invalMessages = NULL;
bool RelcacheInitFileInval = false;
bool wrote_xlog = false;
  • xid:获取当前事务的事务 ID。
  • markXidCommitted:检查事务 ID 是否有效,用于后续判断是否需要记录提交。
  • latestXid:用于记录最新的事务 ID。
  • nrelsrels:用于存储需要删除的表文件信息。
  • nchildrenchildren:用于存储子事务的事务 ID。
  • ndroppedstatsdroppedstats:用于存储统计信息的删除记录。
  • nmsgsinvalMessages:用于存储无效化消息。
  • RelcacheInitFileInval:标记是否需要无效化关系缓存初始化文件。
  • wrote_xlog:标记是否写入了 XLOG。

2. 记录逻辑解码的无效化信息

if (XLogLogicalInfoActive()) {
    LogLogicalInvalidations();
}
  • 如果逻辑解码功能启用,则记录逻辑解码相关的无效化信息。
3. 获取提交记录所需的数据
nrels = smgrGetPendingDeletes(true, &rels);
nchildren = xactGetCommittedChildren(&children);
ndroppedstats = pgstat_get_transactional_drops(true, &droppedstats);
if (XLogStandbyInfoActive()) {
    nmsgs = xactGetCommittedInvalidationMessages(&invalMessages,
                                                 &RelcacheInitFileInval);
}
  • smgrGetPendingDeletes:获取当前事务中待删除的表文件信息。
  • xactGetCommittedChildren:获取子事务的事务 ID。
  • pgstat_get_transactional_drops:获取统计信息的删除记录。
  • xactGetCommittedInvalidationMessages:获取无效化消息。

4. 检查是否写入了 XLOG

wrote_xlog = (XactLastRecEnd != 0);
  • 如果 XactLastRecEnd 不为 0,说明当前事务写入了 XLOG。

5. 检查是否需要记录提交

if (!markXidCommitted)
{
    if (nrels != 0 || ndroppedstats != 0) {
        elog(ERROR, "cannot commit a transaction that deleted files but has no xid");
    }
    Assert(nchildren == 0);

    if (nmsgs != 0) {
        LogStandbyInvalidations(nmsgs, invalMessages, RelcacheInitFileInval);
        wrote_xlog = true;
    }
    if (!wrote_xlog)
        goto cleanup;
}
  • 如果事务没有分配事务 ID(xid),则:
    • 确保没有待删除的表文件或统计信息。
    • 如果有无效化消息,则记录这些消息。
    • 如果没有写入 XLOG,则直接跳到清理阶段。

6. 记录提交 XLOG

else
{
    bool replorigin = (replorigin_session_origin != InvalidRepOriginId &&
                       replorigin_session_origin != DoNotReplicateId);

    BufmgrCommit();
    START_CRIT_SECTION();
    MyProc->delayChkptFlags |= DELAY_CHKPT_START;

    XactLogCommitRecord(xactStopTimestamp,
                        nchildren, children, nrels, rels,
                        ndroppedstats, droppedstats,
                        nmsgs, invalMessages,
                        RelcacheInitFileInval,
                        MyXactFlags,
                        InvalidTransactionId, NULL /* plain commit */ );

    if (replorigin) {
        replorigin_session_advance(replorigin_session_origin_lsn, XactLastRecEnd);
    }

    TransactionTreeSetCommitTsData(xid, nchildren, children,
                                   replorigin_session_origin_timestamp,
                                   replorigin_session_origin);
}
  • replorigin:检查是否启用了复制原点功能。
  • BufmgrCommit:通知缓冲区管理器准备提交。
  • START_CRIT_SECTION:进入提交的关键部分,防止并发的检查点操作干扰。
  • XactLogCommitRecord:记录提交的 XLOG 记录。
  • replorigin_session_advance:如果启用了复制原点功能,更新复制原点的 LSN。
  • TransactionTreeSetCommitTsData:设置提交时间戳数据。

7. 决定是否异步提交

if ((wrote_xlog && markXidCommitted &&
     synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
    forceSyncCommit || nrels > 0)
{
    XLogFlush(XactLastRecEnd);
    if (markXidCommitted) {
        TransactionIdCommitTree(xid, nchildren, children);
    }
}
else
{
    XLogSetAsyncXactLSN(XactLastRecEnd);
    if (markXidCommitted) {
        TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);
    }
}
  • 如果需要同步提交(synchronous_commit 配置为非关闭状态),或者事务有文件清理操作,则:
    • 强制刷新 XLOG。
    • 更新 CLOG(提交日志)。
  • 否则:
    • 设置异步提交的 XLOG LSN。
    • 异步更新 CLOG。

8. 离开提交关键部分

if (markXidCommitted)
{
    MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
    END_CRIT_SECTION();
}
  • 如果事务有有效的 xid,则离开提交关键部分,允许检查点继续。

9. 等待同步复制

if (wrote_xlog && markXidCommitted) {
    SyncRepWaitForLSN(XactLastRecEnd, true);
}
  • 如果事务写入了 XLOG 并且有有效的 xid,则等待同步复制完成。

10. 清理

cleanup:
if (rels) {
    pfree(rels);
}
if (ndroppedstats) {
    pfree(droppedstats);
}
  • 释放分配的资源。

11. 返回最新的事务 ID

latestXid = TransactionIdLatest(xid, nchildren, children);
return latestXid;
  • 计算并返回最新的事务 ID。

总结

RecordTransactionCommit 是一个复杂的函数,负责处理事务提交时的多种情况,包括:

  1. 记录提交的 XLOG。
  2. 更新 CLOG 和其他事务状态。
  3. 清理资源。
  4. 等待同步复制。
  5. 支持同步和异步提交。

这个函数是 PostgreSQL 事务提交机制的核心部分,确保事务的原子性和持久性。