相关《Postgresql源码156MultiXact机制分析》总结一个行锁xmax直接放xid。两个行锁xmax放multixactid然后multixactid1 → offsetoffset → 数据。双SLRU存储请参考[《Postgresql源码156MultiXact机制分析》]。(https://blog.csdn.net/jackgo73/article/details/158853458)再来一个行锁的话不会修改现有multixact记录会新增一条multixactid2先把1的数据考过来然后在追加新的。行锁等的是什么锁是从xmax位置读出来的xid然后给事务id加锁所以等的是事务id锁XactLockTableWait。四种multixact日志WAL 类型宏值redo 核心动作触发场景ZERO_OFF_PAGEXLOG_MULTIXACT_ZERO_OFF_PAGE0x00ZeroMultiXactOffsetPage()MXact ID 跨入新 offsets 页面ZERO_MEM_PAGEXLOG_MULTIXACT_ZERO_MEM_PAGE0x10ZeroMultiXactMemberPage()member 偏移量跨入新 members 页面CREATE_IDXLOG_MULTIXACT_CREATE_ID0x20RecordNewMultiXact() 推进计数器两个以上事务对同一行持锁TRUNCATE_IDXLOG_MULTIXACT_TRUNCATE_ID0x30PerformMembersTruncation()PerformOffsetsTruncation()VACUUM 清理过时的 MultiXactmultixact_redo 调试调试SQL-- Session 1:BEGIN;SELECT*FROMtest_multiWHEREid1FORSHARE;-- 此时 xmax 还是普通 xid-- Session 2:BEGIN;SELECT*FROMtest_multiWHEREid1FORSHARE;-- 此时 PostgreSQL 需要创建一个 MultiXact 来容纳两个事务的锁-- 触发 MultiXactIdCreate() - 写入 XLOG_MULTIXACT_CREATE_ID-- 物理日志会立即发到备库在备库上会触发multixact_redo备库再来一个新会话对这一行加share锁这里并没有修改mid7的记录而是直接新增mid8现在有三个事务对这一行加了share锁这时来一个update锁事务ID开始等锁这时不会产生WAL这时再来再来一个事务加share锁会继续加进去没有等锁队列。multixact_redo代码分析voidmultixact_redo(XLogReaderState*record){uint8 infoXLogRecGetInfo(record)~XLR_INFO_MASK;/* MultiXact 记录不使用 backup block */Assert(!XLogRecHasAnyBlockRefs(record));if(infoXLOG_MULTIXACT_ZERO_OFF_PAGE){/* * 处理 ZERO_OFF_PAGE: 零化 offsets SLRU 新页面 * offsets SLRU 存储 MultiXactId - member偏移量 的映射 */intpageno;intslotno;memcpy(pageno,XLogRecGetData(record),sizeof(int));/* 加锁后零化 offsets SLRU 页面, 然后立即写盘 */LWLockAcquire(MultiXactOffsetSLRULock,LW_EXCLUSIVE);slotnoZeroMultiXactOffsetPage(pageno,false);SimpleLruWritePage(MultiXactOffsetCtl,slotno);Assert(!MultiXactOffsetCtl-shared-page_dirty[slotno]);LWLockRelease(MultiXactOffsetSLRULock);}elseif(infoXLOG_MULTIXACT_ZERO_MEM_PAGE){/* * 处理 ZERO_MEM_PAGE: 零化 members SLRU 新页面 * members SLRU 存储实际的 (xid, lock_mode) 成员列表 */intpageno;intslotno;memcpy(pageno,XLogRecGetData(record),sizeof(int));LWLockAcquire(MultiXactMemberSLRULock,LW_EXCLUSIVE);slotnoZeroMultiXactMemberPage(pageno,false);SimpleLruWritePage(MultiXactMemberCtl,slotno);Assert(!MultiXactMemberCtl-shared-page_dirty[slotno]);LWLockRelease(MultiXactMemberSLRULock);}elseif(infoXLOG_MULTIXACT_CREATE_ID){/* * 处理 CREATE_ID: 重建 MultiXact 记录 * 将 MultiXact 的 offset 和 members 写回 SLRU 文件 */xl_multixact_create*xlrec(xl_multixact_create*)XLogRecGetData(record);TransactionId max_xid;inti;/* * 核心动作: 将数据写回 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-mid1,xlrec-moffxlrec-nmembers);/* * 确保 nextXid 不小于记录中提到的任何 XID * 这应该是多余的(这些 XID 在其他 WAL 中也有记录), * 但作为安全措施仍然执行 */max_xidXLogRecGetXid(record);for(i0;ixlrec-nmembers;i){if(TransactionIdPrecedes(max_xid,xlrec-members[i].xid))max_xidxlrec-members[i].xid;}AdvanceNextFullTransactionIdPastXid(max_xid);}elseif(infoXLOG_MULTIXACT_TRUNCATE_ID){/* * 处理 TRUNCATE_ID: 截断旧 MultiXact 数据 */xl_multixact_truncate xlrec;intpageno;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 的健全性检查 */pagenoMultiXactIdToOffsetPage(xlrec.endTruncOff);MultiXactOffsetCtl-shared-latest_page_numberpageno;PerformOffsetsTruncation(xlrec.startTruncOff,xlrec.endTruncOff);LWLockRelease(MultiXactTruncationLock);}elseelog(PANIC,multixact_redo: unknown op code %u,info);}