Postgresql源码(152)Transaction Redo (RM_XACT_ID = 1)

总结

复制代码
xact_redo(XLogReaderState *record)
│
├─ info == XLOG_XACT_COMMIT (0x00)          ── 普通事务提交
│   ├── ParseCommitRecord()                   // 解析WAL中可变长提交记录
│   └── xact_redo_commit()                    // 执行提交重做(核心)
│
├─ info == XLOG_XACT_COMMIT_PREPARED (0x30)  ── 两阶段提交的COMMIT PREPARED
│   ├── ParseCommitRecord()                   // 解析WAL中可变长提交记录
│   ├── xact_redo_commit()                    // 执行提交重做(使用twophase_xid)
│   └── PrepareRedoRemove()                   // 从共享内存删除gxact条目
│
├─ info == XLOG_XACT_ABORT (0x20)            ── 普通事务回滚
│   ├── ParseAbortRecord()                    // 解析WAL中可变长回滚记录
│   └── xact_redo_abort()                     // 执行回滚重做
│
├─ info == XLOG_XACT_ABORT_PREPARED (0x40)   ── 两阶段提交的ABORT PREPARED
│   ├── ParseAbortRecord()                    // 解析WAL中可变长回滚记录
│   ├── xact_redo_abort()                     // 执行回滚重做(使用twophase_xid)
│   └── PrepareRedoRemove()                   // 从共享内存删除gxact条目
│
├─ info == XLOG_XACT_PREPARE (0x10)          ── 两阶段提交的PREPARE
│   └── PrepareRedoAdd()                      // 将预备事务信息加入共享内存
│
├─ info == XLOG_XACT_ASSIGNMENT (0x50)       ── 子事务XID分配(Hot Standby)
│   └── ProcArrayApplyXidAssignment()         // 在备机ProcArray中关联子事务
│
├─ info == XLOG_XACT_INVALIDATIONS (0x60)    ── 缓存失效消息
│   └── (当前忽略,失效消息在commit记录中处理)
│
└─ else ── elog(PANIC)                        // 未知操作码,PANIC

代码注释

c 复制代码
/*
 * xact_redo() -- 事务类WAL记录的重做入口函数
 *
 * 在恢复(recovery)过程中,每当遇到 RM_XACT_ID 类型的WAL记录时,
 * 都会调用此函数。它根据操作码(info)分派到不同的处理逻辑。
 *
 * 参数:
 *   record -- XLogReaderState,包含已读取并解码的WAL记录
 */
void
xact_redo(XLogReaderState *record)
{
    /*
     * 从WAL记录头中提取操作码。
     * XLOG_XACT_OPMASK (0x70) 用于屏蔽掉额外标志位(如 XLOG_XACT_HAS_INFO 等),
     * 仅保留操作类型部分:
     *   0x00 = COMMIT
     *   0x10 = PREPARE
     *   0x20 = ABORT
     *   0x30 = COMMIT_PREPARED
     *   0x40 = ABORT_PREPARED
     *   0x50 = ASSIGNMENT
     *   0x60 = INVALIDATIONS
     */
    uint8       info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK;

    /*
     * 事务类WAL记录不使用backup block机制(FPW全页写)。
     * 事务操作修改的是 pg_xact (CLOG)、pg_subtrans 等专用结构,
     * 不走标准的缓冲区管理器,因此这里断言不应有关联的数据块引用。
     */
    Assert(!XLogRecHasAnyBlockRefs(record));

    /* ================================================================
     * 分支1: XLOG_XACT_COMMIT --- 普通事务提交
     * ================================================================
     *
     * 这是最常见的事务提交路径。对于单语句事务和显式 COMMIT,
     * 主库都会写入此类型的WAL记录。
     */
    if (info == XLOG_XACT_COMMIT)
    {
        xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
        xl_xact_parsed_commit parsed;

        /*
         * ---- ParseCommitRecord() 详解 ----
         * 源码: src/backend/access/rmgrdesc/xactdesc.c:36
         *
         * 功能: 将WAL中紧凑的可变长提交记录解析为结构化的
         *       xl_xact_parsed_commit,方便后续各模块使用。
         *
         * WAL中 xl_xact_commit 是一个变长记录,固定部分只有 xact_time,
         * 后面根据 xinfo 标志位依次追加多个可选段:
         *
         *  xl_xact_commit 在WAL中的布局:
         *  +------------------+
         *  | xact_time        |  固定: 提交时间戳
         *  +------------------+
         *  | xl_xact_xinfo    |  可选: xinfo标志位 (若 XLOG_XACT_HAS_INFO)
         *  +------------------+
         *  | xl_xact_dbinfo   |  可选: 数据库ID和表空间ID (若 XACT_XINFO_HAS_DBINFO)
         *  +------------------+
         *  | xl_xact_subxacts |  可选: 子事务XID列表 (若 XACT_XINFO_HAS_SUBXACTS)
         *  | + TransactionId[]|
         *  +------------------+
         *  | xl_xact_relfile- |  可选: 需要删除的关系文件列表 (若 XACT_XINFO_HAS_RELFILELOCATORS)
         *  |   locators       |
         *  +------------------+
         *  | xl_xact_stats_   |  可选: 需要丢弃的统计信息 (若 XACT_XINFO_HAS_DROPPED_STATS)
         *  |   items          |
         *  +------------------+
         *  | xl_xact_invals   |  可选: 缓存失效消息 (若 XACT_XINFO_HAS_INVALS)
         *  +------------------+
         *  | xl_xact_twophase |  可选: 两阶段事务XID (若 XACT_XINFO_HAS_TWOPHASE)
         *  | + GID字符串      |  可选: 全局事务标识符 (若 XACT_XINFO_HAS_GID)
         *  +------------------+
         *  | xl_xact_origin   |  可选: 复制源信息 (若 XACT_XINFO_HAS_ORIGIN)
         *  +------------------+
         *
         * ParseCommitRecord 按顺序逐段解析上述内容,将指针/计数存入 parsed 结构体:
         *   parsed->xact_time      = 提交时间戳
         *   parsed->xinfo          = 标志位集合
         *   parsed->dbId, tsId     = 数据库/表空间OID
         *   parsed->nsubxacts      = 子事务数量
         *   parsed->subxacts       = 子事务XID数组指针 (指向WAL缓冲区)
         *   parsed->nrels          = 需删除的关系文件数
         *   parsed->xlocators      = RelFileLocator数组指针
         *   parsed->nstats         = 需丢弃的统计项数
         *   parsed->stats          = 统计项数组指针
         *   parsed->nmsgs          = 失效消息数量
         *   parsed->msgs           = SharedInvalidationMessage数组指针
         *   parsed->twophase_xid   = 两阶段事务XID (仅2PC)
         *   parsed->twophase_gid   = 全局事务ID字符串 (仅2PC)
         *   parsed->origin_lsn     = 复制源LSN
         *   parsed->origin_timestamp = 复制源时间戳
         */
        ParseCommitRecord(XLogRecGetInfo(record), xlrec, &parsed);

        /*
         * ---- xact_redo_commit() 详解 ----
         * 源码: src/backend/access/transam/xact.c:5953
         *
         * 功能: 在恢复过程中重放一个事务提交操作。
         *       这是事务redo中最关键、最复杂的函数,执行顺序至关重要。
         *
         * 参数:
         *   parsed    -- 已解析的提交记录
         *   xid       -- 事务ID (普通提交用WAL记录的xid)
         *   lsn       -- WAL记录的结束位置 (EndRecPtr)
         *   origin_id -- 复制源ID
         *
         * 执行步骤(顺序严格):
         *
         *  ┌─────────────────────────────────────────────────────────────┐
         *  │ 步骤1: AdvanceNextFullTransactionIdPastXid(max_xid)        │
         *  │   确保 ShmemVariableCache->nextXid 超过本记录涉及的所有    │
         *  │   XID (包括子事务),保证XID分配器不会分配已用过的ID        │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤2: TransactionTreeSetCommitTsData(...)                 │
         *  │   设置事务提交时间戳(commit_ts),包括主事务和所有子事务     │
         *  │   如果有复制源信息,使用origin_timestamp作为提交时间         │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤3: 标记事务在 pg_xact (CLOG) 中为已提交                │
         *  │   - standbyState == STANDBY_DISABLED (非Hot Standby):      │
         *  │       TransactionIdCommitTree() -- 同步标记                 │
         *  │   - 否则 (Hot Standby 模式):                               │
         *  │     a) RecordKnownAssignedTransactionIds(max_xid)          │
         *  │        登记新发现的XID到KnownAssignedXids                  │
         *  │     b) TransactionIdAsyncCommitTree() -- 异步标记           │
         *  │        (异步是为了避免提前设hint bits导致崩溃后可见性错误)  │
         *  │     c) ExpireTreeKnownAssignedTransactionIds()              │
         *  │        从KnownAssignedXids中移除已提交的事务                │
         *  │     d) ProcessCommittedInvalidationMessages()               │
         *  │        发送缓存失效消息(保持先失效再释放锁的顺序)        │
         *  │     e) StandbyReleaseLockTree() -- 释放AccessExclusive锁   │
         *  │        (如果 XACT_XINFO_HAS_AE_LOCKS 标志设置)            │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤4: replorigin_advance() (若有复制源信息)               │
         *  │   推进复制源的重放进度                                      │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤5: DropRelationFiles() (若有需要删除的关系文件)        │
         *  │   先 XLogFlush(lsn) 确保WAL已持久化,再删除物理文件        │
         *  │   (先刷WAL是因为删除不可逆,必须保证可以重放)              │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤6: pgstat_execute_transactional_drops() (若有统计项)   │
         *  │   丢弃统计信息                                              │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤7: XLogFlush() (若 XACT_COMPLETION_FORCE_SYNC_COMMIT)  │
         *  │   对于需要同步提交的事务(如 CREATE DATABASE),              │
         *  │   强制刷WAL以更新 minRecoveryPoint                         │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤8: XLogRequestWalReceiverReply()                       │
         *  │   如果需要 synchronous_commit = remote_apply 级别反馈,    │
         *  │   通知 walreceiver 立即发送回复                            │
         *  └─────────────────────────────────────────────────────────────┘
         *
         * 图示: xact_redo_commit 在Hot Standby模式下的关键执行顺序
         *
         *  AdvanceNextXid
         *       │
         *       v
         *  SetCommitTs ──> RecordKnownAssigned
         *                       │
         *                       v
         *                  AsyncCommitTree (标记CLOG)
         *                       │
         *                       v
         *                  ExpireKnownAssigned (从KnownAssigned移除)
         *                       │
         *                       v
         *                  ProcessInvalidations (发送失效消息)
         *                       │
         *                       v
         *                  ReleaseLocks (释放AE锁)
         *                       │
         *                       v
         *                  DropFiles / DropStats (删除文件/统计)
         *                       │
         *                       v
         *                  [可选] ForceSync / ReplyFeedback
         */
        xact_redo_commit(&parsed, XLogRecGetXid(record),
                         record->EndRecPtr, XLogRecGetOrigin(record));
    }

    /* ================================================================
     * 分支2: XLOG_XACT_COMMIT_PREPARED --- 两阶段提交的最终提交
     * ================================================================
     *
     * 当执行 COMMIT PREPARED 'gid' 时,主库写入此WAL记录。
     * 与普通 COMMIT 的区别:
     *   1. 使用 parsed.twophase_xid 而非 XLogRecGetXid(record) 作为事务ID,
     *      因为执行COMMIT PREPARED的后端可能有不同的xid
     *   2. 提交完成后需要清理共享内存中的预备事务条目
     */
    else if (info == XLOG_XACT_COMMIT_PREPARED)
    {
        xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
        xl_xact_parsed_commit parsed;

        /* 解析提交记录(同上) */
        ParseCommitRecord(XLogRecGetInfo(record), xlrec, &parsed);

        /*
         * 执行提交重做,注意使用 parsed.twophase_xid
         * 这是PREPARE TRANSACTION时原始事务的XID
         */
        xact_redo_commit(&parsed, parsed.twophase_xid,
                         record->EndRecPtr, XLogRecGetOrigin(record));

        /*
         * ---- PrepareRedoRemove() 详解 ----
         * 源码: src/backend/access/transam/twophase.c:2563
         *
         * 功能: 从共享内存的 TwoPhaseState 中删除对应的预备事务条目(gxact)。
         *       如果该预备事务之前被checkpoint写入了磁盘(pg_twophase/目录),
         *       还需要删除对应的2PC文件。
         *
         * 流程:
         *   1. 在 TwoPhaseState->prepXacts[] 数组中查找匹配 xid 的 gxact
         *   2. 如果找不到,静默返回(恢复中可能重复重放)
         *   3. 如果 gxact->ondisk == true,调用 RemoveTwoPhaseFile() 删除磁盘文件
         *   4. 调用 RemoveGXact() 从 prepXacts 数组中移除,归还到 freelist
         *
         *  TwoPhaseState 共享内存结构:
         *  ┌──────────────────────────────────────┐
         *  │ TwoPhaseState                        │
         *  │  ├─ freeGXacts ──> gxact ──> gxact   │  空闲链表
         *  │  ├─ numPrepXacts = N                 │
         *  │  └─ prepXacts[0..N-1]                │  活跃预备事务数组
         *  │       ├─ [0] ──> gxact {xid, gid,    │
         *  │       │          inredo, ondisk, ...} │
         *  │       ├─ [1] ──> gxact {...}         │
         *  │       └─ ...                          │
         *  └──────────────────────────────────────┘
         *
         * PrepareRedoRemove 把目标 gxact 从 prepXacts[] 移走,
         * 放回 freeGXacts 链表。
         */
        LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
        PrepareRedoRemove(parsed.twophase_xid, false);
        LWLockRelease(TwoPhaseStateLock);
    }

    /* ================================================================
     * 分支3: XLOG_XACT_ABORT --- 普通事务回滚
     * ================================================================
     *
     * 当事务执行 ROLLBACK 或异常中止时写入此WAL记录。
     */
    else if (info == XLOG_XACT_ABORT)
    {
        xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record);
        xl_xact_parsed_abort parsed;

        /*
         * ---- ParseAbortRecord() 详解 ----
         * 源码: src/backend/access/rmgrdesc/xactdesc.c:173
         *
         * 功能: 与 ParseCommitRecord 类似,解析WAL中的回滚记录。
         *       回滚记录比提交记录更简单,不包含缓存失效消息(invals)。
         *
         *  xl_xact_abort 在WAL中的布局:
         *  +------------------+
         *  | xact_time        |  固定: 回滚时间戳
         *  +------------------+
         *  | xl_xact_xinfo    |  可选: xinfo标志位
         *  +------------------+
         *  | xl_xact_dbinfo   |  可选: 数据库/表空间ID
         *  +------------------+
         *  | xl_xact_subxacts |  可选: 子事务XID列表
         *  +------------------+
         *  | xl_xact_relfile- |  可选: 需删除的关系文件 (DDL回滚场景)
         *  |   locators       |
         *  +------------------+
         *  | xl_xact_stats_   |  可选: 需丢弃的统计信息
         *  |   items          |
         *  +------------------+
         *  | xl_xact_twophase |  可选: 两阶段事务XID
         *  +------------------+
         *  | xl_xact_origin   |  可选: 复制源信息
         *  +------------------+
         *
         * 注意: 回滚记录没有 XACT_XINFO_HAS_INVALS,因为
         * 回滚不需要发送缓存失效消息。
         */
        ParseAbortRecord(XLogRecGetInfo(record), xlrec, &parsed);

        /*
         * ---- xact_redo_abort() 详解 ----
         * 源码: src/backend/access/transam/xact.c:6108
         *
         * 功能: 在恢复过程中重放一个事务回滚操作。
         *       与 xact_redo_commit 相似但更简单。
         *
         * 重要区别: abort 可以是针对子事务及其子孙的,不一定是顶层事务。
         * 因此 topxid != xid 是可能的(而 commit 中 topxid 总等于 xid,
         * 因为子事务提交不会单独写WAL)。
         *
         * 执行步骤:
         *
         *  ┌─────────────────────────────────────────────────────────────┐
         *  │ 步骤1: AdvanceNextFullTransactionIdPastXid(max_xid)        │
         *  │   与commit相同,确保nextXid足够大                          │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤2: 标记事务在 pg_xact (CLOG) 中为已回滚               │
         *  │   - standbyState == STANDBY_DISABLED:                      │
         *  │       TransactionIdAbortTree() -- 直接标记                  │
         *  │   - 否则 (Hot Standby):                                    │
         *  │     a) RecordKnownAssignedTransactionIds(max_xid)          │
         *  │     b) TransactionIdAbortTree() -- 标记CLOG                 │
         *  │     c) ExpireTreeKnownAssignedTransactionIds()              │
         *  │        从KnownAssignedXids中移除                           │
         *  │     d) StandbyReleaseLockTree() -- 释放AE锁               │
         *  │        (注意: 回滚不需要发送缓存失效消息)                  │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤3: replorigin_advance() (若有复制源信息)               │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤4: DropRelationFiles() (若有需要删除的关系文件)        │
         *  │   场景: DDL操作在abort时需要清理已创建的文件                │
         *  │   例如 CREATE TABLE 后 ROLLBACK 需要删除已创建的数据文件   │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤5: pgstat_execute_transactional_drops() (若有统计项)   │
         *  └─────────────────────────────────────────────────────────────┘
         *
         * xact_redo_commit vs xact_redo_abort 对比:
         *
         *  ┌────────────────────┬──────────────┬──────────────┐
         *  │     操作           │ redo_commit  │ redo_abort   │
         *  ├────────────────────┼──────────────┼──────────────┤
         *  │ AdvanceNextXid     │     ✓        │     ✓        │
         *  │ SetCommitTs        │     ✓        │     ✗        │
         *  │ MarkCLOG           │   Commit     │   Abort      │
         *  │ ProcessInvalidation│     ✓        │     ✗        │
         *  │ ReleaseLocks       │     ✓        │     ✓        │
         *  │ DropFiles          │     ✓        │     ✓        │
         *  │ DropStats          │     ✓        │     ✓        │
         *  │ ForceSyncCommit    │     ✓        │     ✗        │
         *  │ ReplyFeedback      │     ✓        │     ✗        │
         *  └────────────────────┴──────────────┴──────────────┘
         */
        xact_redo_abort(&parsed, XLogRecGetXid(record),
                        record->EndRecPtr, XLogRecGetOrigin(record));
    }

    /* ================================================================
     * 分支4: XLOG_XACT_ABORT_PREPARED --- 两阶段提交的最终回滚
     * ================================================================
     *
     * 当执行 ROLLBACK PREPARED 'gid' 时写入此WAL记录。
     * 逻辑与 XLOG_XACT_COMMIT_PREPARED 对称:
     *   1. 使用 parsed.twophase_xid 执行回滚重做
     *   2. 从共享内存清理预备事务条目
     */
    else if (info == XLOG_XACT_ABORT_PREPARED)
    {
        xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record);
        xl_xact_parsed_abort parsed;

        ParseAbortRecord(XLogRecGetInfo(record), xlrec, &parsed);
        xact_redo_abort(&parsed, parsed.twophase_xid,
                        record->EndRecPtr, XLogRecGetOrigin(record));

        /* 删除TwoPhaseState中的gxact条目和/或2PC磁盘文件 */
        LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
        PrepareRedoRemove(parsed.twophase_xid, false);
        LWLockRelease(TwoPhaseStateLock);
    }

    /* ================================================================
     * 分支5: XLOG_XACT_PREPARE --- 两阶段提交的PREPARE阶段
     * ================================================================
     *
     * 当执行 PREPARE TRANSACTION 'gid' 时写入此WAL记录。
     * PREPARE阶段不提交也不回滚事务,而是将事务状态"冻结",
     * 等待后续的 COMMIT PREPARED 或 ROLLBACK PREPARED。
     */
    else if (info == XLOG_XACT_PREPARE)
    {
        /*
         * ---- PrepareRedoAdd() 详解 ----
         * 源码: src/backend/access/transam/twophase.c:2461
         *
         * 功能: 在恢复过程中,将预备事务的信息存入共享内存的
         *       TwoPhaseState 结构中。这样后续的 COMMIT PREPARED / 
         *       ROLLBACK PREPARED 重做时能找到对应的条目。
         *
         * 参数:
         *   buf        -- WAL记录的数据部分(包含TwoPhaseFileHeader + GID等)
         *   start_lsn  -- WAL记录的开始位置 (ReadRecPtr)
         *   end_lsn    -- WAL记录的结束位置 (EndRecPtr)
         *   origin_id  -- 复制源ID
         *
         * 执行步骤:
         *
         *  ┌─────────────────────────────────────────────────────────────┐
         *  │ 步骤1: 解析 TwoPhaseFileHeader                            │
         *  │   从buf中获取 hdr->xid, hdr->owner, hdr->prepared_at 等   │
         *  │   跳过header获取 GID 字符串                                │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤2: 检查重复                                            │
         *  │   如果 start_lsn 有效,检查 pg_twophase/{xid} 文件是否存在│
         *  │   如果已存在(checkpoint期间崩溃导致),跳过以避免重复      │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤3: 从 freeGXacts 链表获取一个空闲 gxact               │
         *  │   填充字段:                                                │
         *  │     gxact->xid = hdr->xid                                 │
         *  │     gxact->prepare_start_lsn = start_lsn                  │
         *  │     gxact->prepare_end_lsn = end_lsn                      │
         *  │     gxact->inredo = true   (标记为恢复阶段添加的)          │
         *  │     gxact->ondisk = (start_lsn无效时为true)               │
         *  │     gxact->valid = false                                   │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤4: 插入 prepXacts[] 活跃数组                          │
         *  │   TwoPhaseState->prepXacts[numPrepXacts++] = gxact        │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤5: replorigin_advance() (若有复制源)                   │
         *  └─────────────────────────────────────────────────────────────┘
         *
         * 两阶段事务在恢复中的生命周期:
         *
         *  PREPARE WAL记录 ──> PrepareRedoAdd()
         *       │                  │
         *       │            gxact加入prepXacts[]
         *       │                  │
         *       v                  v
         *  COMMIT/ABORT WAL ──> xact_redo_commit/abort()
         *       │                  │
         *       │            PrepareRedoRemove()
         *       │                  │
         *       v                  v
         *  gxact从prepXacts[]移除,归还freeGXacts
         */
        LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
        PrepareRedoAdd(XLogRecGetData(record),
                       record->ReadRecPtr,
                       record->EndRecPtr,
                       XLogRecGetOrigin(record));
        LWLockRelease(TwoPhaseStateLock);
    }

    /* ================================================================
     * 分支6: XLOG_XACT_ASSIGNMENT --- 子事务XID分配
     * ================================================================
     *
     * 当主库在一个事务中累积了 PGPROC_MAX_CACHED_SUBXIDS (64) 个
     * 未报告的子事务XID时,写入此WAL记录。
     *
     * 目的: 告知 Hot Standby 备机哪些子事务属于哪个顶层事务,
     * 以限制备机共享内存中 KnownAssignedXids 的大小。
     *
     * 此记录仅在 standbyState >= STANDBY_INITIALIZED 时才有实际意义。
     */
    else if (info == XLOG_XACT_ASSIGNMENT)
    {
        xl_xact_assignment *xlrec = (xl_xact_assignment *) XLogRecGetData(record);

        /*
         * xl_xact_assignment 结构:
         *   xtop      -- 顶层事务XID
         *   nsubxacts  -- 子事务数量
         *   xsub[]    -- 子事务XID数组 (变长)
         */

        /*
         * ---- ProcArrayApplyXidAssignment() 详解 ----
         * 源码: src/backend/storage/ipc/procarray.c:1313
         *
         * 功能: 在备机上处理子事务XID分配记录。
         *       将子事务与顶层事务的映射关系记录到 pg_subtrans,
         *       并维护 KnownAssignedXids 数据结构。
         *
         * 执行步骤:
         *
         *  ┌─────────────────────────────────────────────────────────────┐
         *  │ 步骤1: RecordKnownAssignedTransactionIds(max_xid)          │
         *  │   确保所有涉及的XID都被记录为"已知已分配"                  │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤2: SubTransSetParent(subxids[i], topxid)              │
         *  │   为每个子事务设置父事务ID(写入 pg_subtrans)             │
         *  │   注意: 恢复中直接用顶层xid,跳过中间层级                  │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤3: 如果 standbyState == STANDBY_INITIALIZED,到此结束 │
         *  │   (KnownAssignedXids尚未完全维护)                          │
         *  ├─────────────────────────────────────────────────────────────┤
         *  │ 步骤4: (standbyState > STANDBY_INITIALIZED)               │
         *  │   获取 ProcArrayLock (LW_EXCLUSIVE)                        │
         *  │   将这些子事务XID从 KnownAssignedXids 中移除              │
         *  │   (因为已经通过pg_subtrans关联到顶层事务,                 │
         *  │    不再需要在KnownAssignedXids中单独跟踪)                  │
         *  │   释放 ProcArrayLock                                       │
         *  └─────────────────────────────────────────────────────────────┘
         *
         * 为什么需要这个机制:
         *
         *  主库                          备机
         *  ┌─────────────┐         ┌──────────────────────┐
         *  │ 顶层事务 T1 │         │ KnownAssignedXids    │
         *  │  ├─ sub1    │  WAL    │ [T1, sub1, sub2, ... │
         *  │  ├─ sub2    │ ────>   │  sub64]              │
         *  │  ├─ ...     │         │                      │
         *  │  └─ sub64   │         │ 空间有限! 需要清理   │
         *  └─────────────┘         └──────────────────────┘
         *                                    │
         *                          XLOG_XACT_ASSIGNMENT 到达
         *                                    │
         *                                    v
         *                          ┌──────────────────────┐
         *                          │ pg_subtrans:          │
         *                          │   sub1->T1            │
         *                          │   sub2->T1            │
         *                          │   ...->T1             │
         *                          │ KnownAssignedXids:    │
         *                          │ [T1]  (子事务已移除) │
         *                          └──────────────────────┘
         */
        if (standbyState >= STANDBY_INITIALIZED)
            ProcArrayApplyXidAssignment(xlrec->xtop,
                                        xlrec->nsubxacts, xlrec->xsub);
    }

    /* ================================================================
     * 分支7: XLOG_XACT_INVALIDATIONS --- 缓存失效消息
     * ================================================================
     *
     * 此类WAL记录包含事务中间产生的缓存失效消息。
     * 
     * 当前在恢复中被忽略。原因:
     *   真正需要执行的失效消息已经嵌入在 COMMIT 记录中
     *   (通过 XACT_XINFO_HAS_INVALS 标志),
     *   并在 xact_redo_commit -> ProcessCommittedInvalidationMessages()
     *   中处理。
     *
     * 此WAL记录的存在主要是为了逻辑解码(logical decoding)使用,
     * 让解码器能在事务未提交时就看到失效消息。
     */
    else if (info == XLOG_XACT_INVALIDATIONS)
    {
        /*
         * XXX 当前忽略此记录,真正重要的失效消息在提交记录中处理。
         */
    }

    /* ================================================================
     * 默认分支: 未知操作码 --- PANIC
     * ================================================================
     * 如果遇到无法识别的操作码,说明WAL记录已损坏或版本不兼容,
     * 必须PANIC停机,不允许继续恢复(否则可能导致数据不一致)。
     */
    else
        elog(PANIC, "xact_redo: unknown op code %u", info);
}

数据结构

xl_xact_parsed_commit

c 复制代码
/* 源码: src/include/access/xact.h:371 */
typedef struct xl_xact_parsed_commit
{
    TimestampTz xact_time;          // 提交时间戳
    uint32      xinfo;              // 标志位集合

    Oid         dbId;               // 数据库OID
    Oid         tsId;               // 表空间OID

    int         nsubxacts;          // 子事务数量
    TransactionId *subxacts;        // 子事务XID数组

    int         nrels;              // 需删除的关系文件数
    RelFileLocator *xlocators;      // 关系文件定位器数组

    int         nstats;             // 需丢弃的统计项数
    xl_xact_stats_item *stats;      // 统计项数组

    int         nmsgs;              // 缓存失效消息数
    SharedInvalidationMessage *msgs; // 失效消息数组

    TransactionId twophase_xid;     // 两阶段事务XID (仅2PC)
    char        twophase_gid[GIDSIZE]; // 全局事务ID (仅2PC)
    int         nabortrels;         // 2PC abort时需删除的关系数
    RelFileLocator *abortlocators;  // 2PC abort关系定位器
    int         nabortstats;        // 2PC abort统计项数
    xl_xact_stats_item *abortstats; // 2PC abort统计项

    XLogRecPtr  origin_lsn;         // 复制源LSN
    TimestampTz origin_timestamp;   // 复制源时间戳
} xl_xact_parsed_commit;

xl_xact_assignment

c 复制代码
/* 源码: src/include/access/xact.h:219 */
typedef struct xl_xact_assignment
{
    TransactionId xtop;          // 顶层事务XID
    int           nsubxacts;     // 子事务数量
    TransactionId xsub[FLEXIBLE_ARRAY_MEMBER]; // 子事务XID变长数组
} xl_xact_assignment;

TwoPhaseState 共享内存布局

复制代码
共享内存 (TwoPhaseState)
┌───────────────────────────────-──────────────────┐
│ freeGXacts ──> [gxact] ──> [gxact] ──> NULL      │  空闲链表
│                                                  │
│ numPrepXacts = 2                                 │  当前活跃的预备事务数
│                                                  │
│ prepXacts[0] ──> GlobalTransactionData {         │
│                    xid = 100                     │
│                    gid = "my_2pc_txn"            │
│                    inredo = true                 │  恢复阶段添加的
│                    ondisk = false                │  尚未写入磁盘
│                    prepare_start_lsn = 0/1234    │
│                    prepare_end_lsn   = 0/1280    │
│                  }                               │
│                                                  │
│ prepXacts[1] ──> GlobalTransactionData {         │
│                    xid = 105                     │
│                    gid = "dist_txn_42"           │
│                    inredo = true                 │
│                    ondisk = true                 │  已由checkpoint写盘
│                    ...                           │
│                  }                               │
│                                                  │
│ prepXacts[2..max_prepared_xacts-1] = 未使用       │
└─────────────────────────────-────────────────────┘
相关推荐
Zzzzmo_2 小时前
【MySQL】视图
数据库·mysql
就不掉头发2 小时前
Linux与数据库
linux·运维·数据库
Predestination王瀞潞2 小时前
基于 `SqlSession` 的事务手动管理机制
数据库·oracle·java-ee
李慕婉学姐2 小时前
Springboot传统文化服饰交流平台k79z52ic(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
Predestination王瀞潞2 小时前
映射文件中的四大核心 CRUD 操作标签(对应数据库的增、删、改、查操作)
数据库·oracle
IvorySQL2 小时前
PostgreSQL 技术日报 (3 月 12 日)|为什么加索引反而变慢?这招让查询快 50 倍
数据库·postgresql·开源
y = xⁿ2 小时前
【从零开始学习Redis|第五篇】Redis 常见数据类型和应用场景
数据库·redis·学习·缓存
DolphinDB智臾科技3 小时前
DolphinDB:技术赋能钢铁业,国产时序数据库领路数智化落地
数据库·物联网·时序数据库·dolphindb