Postgresql源码(157)Redo系列MultiXact Redo (RM_MULTIXACT_ID = 6)

相关
《Postgresql源码(156)MultiXact机制分析》

总结

  • 一个行锁,xmax直接放xid。两个行锁,xmax放multixactid,然后multixactid=1 → offset,offset → 数据。双SLRU存储请参考[《Postgresql源码(156)MultiXact机制分析》]。(https://blog.csdn.net/jackgo73/article/details/158853458)
  • 再来一个行锁的话,不会修改现有multixact记录,会新增一条multixactid=2,先把1的数据考过来,然后在追加新的。
  • 行锁等的是什么锁?是从xmax位置读出来的xid,然后给事务id加锁,所以等的是事务id锁XactLockTableWait
  • 四种multixact日志:
WAL 类型 redo 核心动作 触发场景
ZERO_OFF_PAGE XLOG_MULTIXACT_ZERO_OFF_PAGE 0x00 ZeroMultiXactOffsetPage() MXact ID 跨入新 offsets 页面
ZERO_MEM_PAGE XLOG_MULTIXACT_ZERO_MEM_PAGE 0x10 ZeroMultiXactMemberPage() member 偏移量跨入新 members 页面
CREATE_ID XLOG_MULTIXACT_CREATE_ID 0x20 RecordNewMultiXact() + 推进计数器 两个以上事务对同一行持锁
TRUNCATE_ID XLOG_MULTIXACT_TRUNCATE_ID 0x30 PerformMembersTruncation() + PerformOffsetsTruncation() VACUUM 清理过时的 MultiXact

multixact_redo 调试

调试SQL

sql 复制代码
-- Session 1:
BEGIN;
SELECT * FROM test_multi WHERE id = 1 FOR SHARE;
-- 此时 xmax 还是普通 xid

-- Session 2:
BEGIN;
SELECT * FROM test_multi WHERE id = 1 FOR SHARE;
-- 此时 PostgreSQL 需要创建一个 MultiXact 来容纳两个事务的锁
-- 触发 MultiXactIdCreate() -> 写入 XLOG_MULTIXACT_CREATE_ID

-- 物理日志会立即发到备库,在备库上会触发multixact_redo

备库:

再来一个新会话对这一行加share锁,这里并没有修改mid=7的记录,而是直接新增mid=8:

现在有三个事务对这一行加了share锁,这时来一个update锁事务ID,开始等锁,这时不会产生WAL:

这时再来再来一个事务加share锁,会继续加进去,没有等锁队列。

multixact_redo代码分析

c 复制代码
void
multixact_redo(XLogReaderState *record)
{
    uint8       info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

    /* MultiXact 记录不使用 backup block */
    Assert(!XLogRecHasAnyBlockRefs(record));

    if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE)
    {
        /*
         * ===== 处理 ZERO_OFF_PAGE: 零化 offsets SLRU 新页面 =====
         * offsets SLRU 存储 MultiXactId -> member偏移量 的映射
         */
        int         pageno;
        int         slotno;

        memcpy(&pageno, XLogRecGetData(record), sizeof(int));

        /* 加锁后零化 offsets SLRU 页面, 然后立即写盘 */
        LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);

        slotno = ZeroMultiXactOffsetPage(pageno, false);
        SimpleLruWritePage(MultiXactOffsetCtl, slotno);
        Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);

        LWLockRelease(MultiXactOffsetSLRULock);
    }
    else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
    {
        /*
         * ===== 处理 ZERO_MEM_PAGE: 零化 members SLRU 新页面 =====
         * members SLRU 存储实际的 (xid, lock_mode) 成员列表
         */
        int         pageno;
        int         slotno;

        memcpy(&pageno, XLogRecGetData(record), sizeof(int));

        LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);

        slotno = ZeroMultiXactMemberPage(pageno, false);
        SimpleLruWritePage(MultiXactMemberCtl, slotno);
        Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);

        LWLockRelease(MultiXactMemberSLRULock);
    }
    else if (info == XLOG_MULTIXACT_CREATE_ID)
    {
        /*
         * ===== 处理 CREATE_ID: 重建 MultiXact 记录 =====
         * 将 MultiXact 的 offset 和 members 写回 SLRU 文件
         */
        xl_multixact_create *xlrec =
            (xl_multixact_create *) XLogRecGetData(record);
        TransactionId max_xid;
        int         i;

        /*
         * 核心动作: 将数据写回 SLRU 文件
         * RecordNewMultiXact 会:
         *   1. 在 offsets SLRU 中记录 mid -> moff 的映射
         *   2. 在 members SLRU 中写入 nmembers 个 MultiXactMember
         */
        RecordNewMultiXact(xlrec->mid, xlrec->moff, xlrec->nmembers,
                           xlrec->members);

        /*
         * 推进 nextMXact 和 nextOffset 计数器
         * 确保它们不小于本记录中的值, 避免重复分配
         */
        MultiXactAdvanceNextMXact(xlrec->mid + 1,
                                  xlrec->moff + xlrec->nmembers);

        /*
         * 确保 nextXid 不小于记录中提到的任何 XID
         * 这应该是多余的(这些 XID 在其他 WAL 中也有记录),
         * 但作为安全措施仍然执行
         */
        max_xid = XLogRecGetXid(record);
        for (i = 0; i < xlrec->nmembers; i++)
        {
            if (TransactionIdPrecedes(max_xid, xlrec->members[i].xid))
                max_xid = xlrec->members[i].xid;
        }

        AdvanceNextFullTransactionIdPastXid(max_xid);
    }
    else if (info == XLOG_MULTIXACT_TRUNCATE_ID)
    {
        /*
         * ===== 处理 TRUNCATE_ID: 截断旧 MultiXact 数据 =====
         */
        xl_multixact_truncate xlrec;
        int         pageno;

        memcpy(&xlrec, XLogRecGetData(record), SizeOfMultiXactTruncate);

        elog(DEBUG1, "replaying multixact truncation: "
             "offsets [%u, %u), offsets segments [%x, %x), "
             "members [%u, %u), members segments [%x, %x)",
             xlrec.startTruncOff, xlrec.endTruncOff,
             MultiXactIdToOffsetSegment(xlrec.startTruncOff),
             MultiXactIdToOffsetSegment(xlrec.endTruncOff),
             xlrec.startTruncMemb, xlrec.endTruncMemb,
             MXOffsetToMemberSegment(xlrec.startTruncMemb),
             MXOffsetToMemberSegment(xlrec.endTruncMemb));

        /* 获取截断锁, 防止并发操作 */
        LWLockAcquire(MultiXactTruncationLock, LW_EXCLUSIVE);

        /*
         * 推进 horizon 值(最老 MultiXact 限制), 确保恢复结束时是最新的
         */
        SetMultiXactIdLimit(xlrec.endTruncOff, xlrec.oldestMultiDB, false);

        /* 截断 members SLRU 段文件 */
        PerformMembersTruncation(xlrec.startTruncMemb, xlrec.endTruncMemb);

        /*
         * 截断 offsets SLRU 段文件
         * 在回放期间 latest_page_number 可能尚未设置,
         * 需要手动插入一个合适的值来绕过 SimpleLruTruncate 的健全性检查
         */
        pageno = MultiXactIdToOffsetPage(xlrec.endTruncOff);
        MultiXactOffsetCtl->shared->latest_page_number = pageno;
        PerformOffsetsTruncation(xlrec.startTruncOff, xlrec.endTruncOff);

        LWLockRelease(MultiXactTruncationLock);
    }
    else
        elog(PANIC, "multixact_redo: unknown op code %u", info);
}
相关推荐
05大叔1 小时前
mysql 触发器,锁
数据库·mysql·oracle
大鹏说大话1 小时前
拒绝“慢查询”:SQL性能优化实战与索引的双刃剑效应
数据库·oracle
小狼只想在飞船上收庄稼1 小时前
Linux 信号机制--续1
数据库
MoSTChillax2 小时前
新手 3 个文件跑通前端 + Flask + MySQL(最小可行 CRUD)
数据库·python·mysql·flask
梦想的旅途22 小时前
企微客户自动触达 API:实现全生命周期的自动化消息路由
数据库·自动化·企业微信
shyの同学2 小时前
SQL 谓词下推带来的潜在问题
数据库·sql·mysql
x_lrong2 小时前
LangChain&Redis记忆
数据库·redis·langchain·向量数据库
代码探秘者2 小时前
【Redis】双写一致性:延迟双删 / 读写锁 / 异步通知 / Canal,一文全解
java·数据库·redis·后端·算法·缓存
西柚小萌新2 小时前
【数据库】--PostgreSQL 详细安装教程
数据库·postgresql