7.11.5 XLOG日志管理器

模块概述

XLOG(又称WAL日志,Write-Ahead Log,预写日志)是PostgreSQL实现事务持久性的核心组件,对应传统数据库理论中的“事务日志”。其核心原则是“先写日志,后写数据”,详细记录所有服务进程对数据库的修改操作,为系统崩溃恢复、数据一致性保障提供基础。

XLOG日志的核心存储与结构特性如下:

  • 存储方式:日志文件按16MB大小分割为独立段文件,通过“XLOG文件号+段文件号”唯一标识段文件;单个日志记录的地址由“文件号+文件内偏移量”定位,通过64位指针XLogRecPtr存储,避免地址溢出。

  • 内存管理:日志在内存中按页存放,单页大小8KB,每页包含头部信息与日志记录主体;段文件的第一个页面使用“长头部”(LongHeader),其余页面使用普通头部,通过头部标志位区分。

  • 跨页存储:长日志记录可跨页存储,但日志头部XLogRecord必须在单个页面内完整存放;若页面剩余空间不足存放头部,剩余空间将被舍弃,日志从下一页开始存储。

1. XLOG日志管理器相关数据结构

XLOG日志的完整生命周期依赖多类数据结构,分别管理页面头部、记录控制信息、数据内容、全局控制状态及检查点,各结构功能与关联逻辑如下:

1.1 日志页面头部信息

用于描述单个XLOG页面的元数据,分为普通头部与长头部两类,长头部仅用于段文件的第一个页面,提供更精细的文件定位信息。

(1)普通页面头部:XLogPageHeaderData(数据结构7.27)

存储单个日志页面的基础元数据,结构定义及字段说明如下:


// 数据结构7.27 XLogPageHeaderData
struct XLogPageHeaderData
{
    uint16  xlp_magic;        // 校验位(用于验证页面完整性)
    uint16  xlp_info;         // 标志位(占2位,区分页面类型与记录连续性)
    TimeLineID xlp_tli;       // 时间序列ID(页面第一条记录所属的时间线)
    XLogRecPtr xlp_pageaddr;  // 页面首地址(对应XLOG文件中的绝对位置)
};

标志位xlp_info取值说明:

  • 0:表示当前页第一个日志记录是上一页最后一条记录的延续,需结合XLogContRecord读取完整记录;

  • 1:表示当前页为段文件第一页,使用长头部XLogLongPageHeaderData

(2)长页面头部:XLogLongPageHeaderData(数据结构7.28)

继承普通头部结构,补充系统级标识信息,仅用于段文件首页,结构定义如下:


// 数据结构7.28 XLogLongPageHeaderData
typedef struct XLogLongPageHeaderData
{
    XLogPageHeaderData std;   // 继承普通页面头部信息
    uint64  xlp_sysid;        // 系统标识符(与pg_control文件一致,验证日志归属)
    uint32  xlp_seg_size;     // 段文件大小(固定16MB,用于校验)
    uint32  xlp_xlog_blcksz;  // 日志块大小(固定8KB,与页面大小一致)
} XLogLongPageHeaderData;
(3)跨页记录续体:XLogContRecord(数据结构7.29)

当日志记录跨页存储时,用于描述当前页存储的部分记录剩余数据长度,结构定义如下:


// 数据结构7.29 XLogContRecord
typedef struct XLogContRecord
{
    uint32  xl_rem_len;  // 剩余数据总长度(后续页面需读取的字节数)
} XLogContRecord;

1.2 日志记录控制信息

单条XLOG记录由“头部+资源数据+备份块”三部分组成,最多可附带3个备份块(每个8KB,对应一个磁盘块),结构如下表所示:

组成部分 说明
XLogRecord 日志记录头部,存储校验码、位置、长度、资源管理器等控制信息
Data of RMGR 资源管理器数据,长度由xl_len指定,对应具体操作的数据
Backup Block 1~3 备份块,每个包含BkpBlock头部+8KB备份数据,用于恢复时还原磁盘块
(1)日志头部:XLogRecord(数据结构7.30)

单条日志的核心控制结构,定义如下:


// 数据结构7.30 XLogRecord
typedef struct XLogRecord
{
    crc64       xl_crc;    // CRC校验码(验证记录完整性,防止数据损坏)
    XLogRecPtr  xl_prev;   // 前一条日志记录的地址(构建日志链表)
    TransactionId xl_xid;  // 对应事务ID(标识日志所属事务)
    uint32      xl_tot_len;// 整条记录总长度(含头部、数据、备份块)
    uint32      xl_len;    // 资源管理器数据长度(不含备份块)
    uint8       xl_info;   // 信息标志位(高4位表操作类型,低4位表备份块标识)
    RmgrId      xl_rmid;   // 资源管理器ID(标识日志所属数据类型)
} XLogRecord;
(2)资源管理器ID(xl_rmid

用于分类日志数据,恢复时可快速匹配对应处理函数,核心取值如下:

  • RM_XLOG_ID:检查点相关日志;

  • RM_XACT_ID:事务提交/中止日志;

  • RM_HEAP_ID:堆表元组插入/删除/更新日志;

  • RM_CLOG_ID:CLOG页面初始化日志;

  • RM_BTREE_ID:B树索引修改日志。

(3)信息标志位(xl_info

高4位由资源管理器定义,表操作类型;低4位标识备份块状态,核心取值如下:

  • 高4位示例:

    • XLOG_XACT_COMMIT:事务提交;

    • XLOG_HEAP_INSERT:堆表插入元组。

  • 低4位示例(仅用3位):

    • XLR_BKP_BLOCK_1:存在第一个备份块;

    • XLR_BKP_BLOCK_2/3:存在第二个/第三个备份块。

1.3 日志记录数据信息

存储日志对应的具体数据及备份块元信息,核心结构如下:

(1)日志数据链表:XLogRecData(数据结构7.31)

采用链表结构存储资源管理器数据,支持多段数据拼接,定义如下:


// 数据结构7.31 XLogRecData
typedef struct XLogRecData
{
    char               *data;      // 资源管理器数据指针
    uint32              len;       // 数据长度(字节数)
    Buffer              buffer;    // 数据关联的缓冲区(内存中的磁盘块)
    bool                buffer_std;// 缓冲区是否按标准格式存储
    struct XLogRecData *next;      // 下一个数据节点指针(链表串联多段数据)
} XLogRecData;
(2)备份块头部:BkpBlock(数据结构7.32)

描述备份块的位置及空洞信息,支持“空洞备份”优化(仅记录空洞位置,不备份空洞内容,提升效率),定义如下:


// 数据结构7.32 BkpBlock
typedef struct BkpBlock
{
    RelFileNode node;        // 表节点(标识备份块所属的表/索引文件)
    BlockNumber block;       // 块号(文件内的磁盘块编号)
    uint16      hole_offset; // 空洞偏移量(块中空洞的起始位置)
    uint16      hole_length; // 空洞长度(无需备份的字节数)
} BkpBlock;
(3)日志地址指针:XLogRecPtr(数据结构7.33)

64位指针,唯一标识XLOG日志中的位置,定义如下:


// 数据结构7.33 XLogRecPtr
typedef struct XLogRecPtr
{
    uint32 xlogid;   // 日志文件号
    uint32 xrecoff;  // 文件内偏移量(字节数)
} XLogRecPtr;

1.4 XLOG全局控制结构

XLogCtlData(数据结构7.34)存储于共享内存,管理XLOG的全局状态、缓冲区、写入请求等,定义如下(修正原文字段名错误):


// 数据结构7.34 XLogCtlData
typedef struct XLogCtlData
{
    XLogCtlInsert  Insert;        // 日志插入状态(最新插入位置、缓冲区信息)
    XLogwrtRqst    LogwrtRqst;    // 日志写入请求(需写入/同步到磁盘的位置)
    XLogwrtResult  LogwrtResult;  // 日志写入结果(已完成写入/同步的位置)
    uint32         ckptXidEpoch;  // 检查点事务ID的纪元(区分事务ID循环)
    TransactionId  ckptxid;       // 检查点对应的事务ID
    XLogRecPtr     asyncCommitLSN;// 最近异步提交的日志序列号(LSN)
    XLogCtlWrite   Write;         // 日志写入控制信息
    char           *pages;        // 共享内存缓冲区数组指针(存储日志页面)
    XLogRecPtr     *xlblocks;     // 缓冲区对应XLOG文件的地址指针
    Size           XLogCacheByte; // 日志缓冲区总大小(字节)
    int            XLogCacheBlck; // 缓冲区最大块下标(控制缓冲区容量)
    TimeLineID     ThisTimeLineID;// 当前时间线ID(用于主从复制与恢复)
    slock_t        info_lck;      // 共享锁(保护LogwrtRqst/LogwrtResult并发访问)
} XLogCtlData;

核心字段说明:LogwrtRqst指定需写入的日志位置,LogwrtResult反馈已写入位置,两者配合实现日志写入的并发控制。

1.5 检查点结构与策略

PostgreSQL仅使用REDO恢复(依赖MVCC多版本无需UNDO),检查点用于标记日志恢复起点,减少恢复时间,核心结构与规则如下:

(1)检查点结构:CheckPoint(数据结构7.35)

// 数据结构7.35 CheckPoint
typedef struct CheckPoint
{
    XLogRecPtr     redo;           // 恢复起点(从此位置开始执行REDO)
    TimeLineID     ThisTimeLineID; // 当前时间线ID
    uint32         nextxidEpoch;   // 下一个事务ID的纪元
    TransactionId  nextxid;        // 下一个可用事务ID
    Oid            nextoid;        // 下一个可用OID(对象标识符)
    MultiXactId    nextMulti;      // 下一个可用MultiXactID
    MultiXactOffset nextMultioffset;// 下一个MultiXactID的偏移量
    time_t         time;           // 检查点创建时间戳
} CheckPoint;
(2)检查点创建规则
  1. 标记检查点起点(REDO位置),此阶段禁止其他事务写日志;

  2. 刷新所有脏数据缓冲区(允许事务同时写日志);

  3. 写入检查点日志并刷盘,完成检查点创建。

检查点类型:正常关闭时自动创建(标记XLOG_CHECKPOINT_SHUTDOWN),用户可手动创建(标记XLOG_CHECKPOINT_ONLINE)。

2. XLOG日志管理器主要操作

XLOG操作覆盖日志全生命周期,包括初始化、写入、归档、恢复等,核心操作及流程如下:

2.1 日志系统启动(BootStrapXLOG

系统引导时初始化XLOG,核心功能:

  • 创建第一个XLOG段文件,插入第一条检查点记录;

  • 调用BootStrapCLOGBootStrapSUBTRANSBootStrapMultiXact,初始化其他三类日志模块。

2.2 段文件创建(InstallXLogFileSegment

创建新的16MB段文件,用于存储后续日志,返回布尔值:

  • 成功创建并定位到目标位置,返回TRUE

  • 无可用空段位置,返回FALSE

2.3 日志插入(XLogInsert

事务执行修改操作(插入/删除/提交等)时调用,向日志写入记录,流程如下:

  1. 备份块判断:无需备份则记录内存头信息与磁盘数据;需备份则记录文件号、块号及磁盘块备份;

  2. 缓冲区检查:根据LogwrtRqstLogwrtResult,若缓冲区使用量超过一半,触发刷盘;

  3. 写入日志:通过XLogCtlInsert字段定位插入点,写入日志头部(确保不跨页)及主体数据;

  4. 返回地址:返回下一条日志的插入地址。

2.4 日志归档(XLogWrite

按写入请求将日志同步到磁盘段文件,需持有WALWriteLock(日志写锁),保障并发写入安全。

2.5 日志刷新(XLogFlush

确保指定位置之前的所有日志均刷盘,无需持有日志写锁,常用于事务提交后的日志持久化。

2.6 日志文件打开(XLogFileInit

创建新段文件或打开已有文件,核心参数说明:

  • use_existent:传入时表是否允许使用已有文件,返回时表是否使用了已有文件;

  • use_lock:是否需要获取ControlFileLock(控制文件锁),仅引导时无需此锁。

流程:优先使用已有文件→无则创建新文件→强制创建时覆盖已有文件。

2.7 日志文件拷贝(XLogFileCopy

恢复阶段专用,通过拷贝已有XLOG文件创建新段文件,无需加锁(恢复阶段无并发写)。

2.8 备份块恢复(RestoreBkpBlocks

恢复日志中的备份块到磁盘,无论数据库页面是否更新,均强制覆盖——防止崩溃时页面写入不完整。

2.9 日志读取(ReadRecord

读取指定位置或下一条日志记录:

  • 指定RecPtr则读取对应位置记录,否则读取上一条记录的下一条;

  • 无有效记录时返回NULL

2.10 检查点创建(CreateCheckPoint

支持4种检查点类型,流程如下:

  • 检查点类型:

    1. CHECKPOINT_IS_SHUTDOWN:数据库关闭时创建;

    2. CHECKPOINT_IMMEDIATE:快速刷新日志(紧急场景使用);

    3. CHECKPOINT_END_OF_RECOVERY:恢复完成后创建;

    4. CHECKPOINT_FORCE:强制创建(即使无日志)。

  • 核心流程:

    1. 更新控制文件(关闭/恢复类检查点);

    2. 跳过重复检查点(与前一个检查点位置一致时);

    3. 等待所有事务提交完成;

    4. 更新全局缓存、控制文件、MULTIXACT信息;

    5. 调用CheckPointGuts刷盘所有日志与缓冲区;

    6. 写入检查点日志并刷盘,更新控制文件与检查点位置。

2.11 恢复检查点创建(RestartPoint

恢复过程中创建的检查点,用于应对恢复阶段的系统崩溃,创建条件:

  • 仅在恢复过程中创建;

  • 恢复位置未包含在已有检查点中时创建。

3. XLOG日志恢复策略

系统崩溃重启后,通过StartupXlog触发恢复,核心逻辑:读取控制文件→定位最新检查点→执行REDO操作→初始化日志模块,具体流程如下:

3.1 恢复触发条件

满足以下任一条件,触发恢复流程:

  1. 日志目录中存在backup_label文件(备份恢复标识);

  2. 控制文件记录的最新检查点无法读取到对应日志;

  3. 控制文件记录的检查点与日志中检查点的REDO位置不一致。

3.2 恢复核心流程

  1. 更新控制信息到ControlFile

  2. 初始化恢复用资源管理器;

  3. 从检查点REDO位置开始,逐条读取后续日志;

  4. 按资源管理器ID匹配对应模块,执行REDO操作(重复日志记录的原操作);

  5. 重复步骤3~4,直至无更多日志记录;

  6. 重建检查点,初始化XLogCtl,启动CLOG、SUBTRANS、MULTIXACT模块。

3.3 典型日志的REDO操作

  • Database类型:无备份块,Create操作拷贝目录,Drop操作删除缓冲区;

  • Heap类型:有备份块则恢复并标记脏页,无备份块则重建元组,执行INSERT/DELETE/UPDATE;

  • B-Tree类型:根据标志位执行叶子节点插入、节点分割等操作;

  • Xlog类型:直接拷贝日志记录信息(如OID分配、检查点设置),恢复简单。

总结

XLOG日志管理器是PostgreSQL事务持久性与数据一致性的核心保障,通过“预写日志”机制与精细的结构设计,实现了日志的高效存储、并发写入与崩溃恢复。其核心特点的是:按16MB段文件分割存储,便于归档与清理;采用双头部设计区分段首页与普通页,支持日志跨页存储但保证头部完整性;通过资源管理器分类日志,配合检查点机制大幅缩短恢复时间。

从功能闭环来看,XLOG涵盖“日志写入-归档-刷盘-恢复”全流程,与CLOG、SUBTRANS、MULTIXACT日志模块深度协同,既通过缓冲区优化提升写入性能,又通过CRC校验、强制刷盘保障数据可靠,是PostgreSQL数据库稳定运行的关键组件。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐