XLOG日志管理器
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)检查点创建规则
-
标记检查点起点(REDO位置),此阶段禁止其他事务写日志;
-
刷新所有脏数据缓冲区(允许事务同时写日志);
-
写入检查点日志并刷盘,完成检查点创建。
检查点类型:正常关闭时自动创建(标记XLOG_CHECKPOINT_SHUTDOWN),用户可手动创建(标记XLOG_CHECKPOINT_ONLINE)。
2. XLOG日志管理器主要操作
XLOG操作覆盖日志全生命周期,包括初始化、写入、归档、恢复等,核心操作及流程如下:
2.1 日志系统启动(BootStrapXLOG)
系统引导时初始化XLOG,核心功能:
-
创建第一个XLOG段文件,插入第一条检查点记录;
-
调用
BootStrapCLOG、BootStrapSUBTRANS、BootStrapMultiXact,初始化其他三类日志模块。
2.2 段文件创建(InstallXLogFileSegment)
创建新的16MB段文件,用于存储后续日志,返回布尔值:
-
成功创建并定位到目标位置,返回
TRUE; -
无可用空段位置,返回
FALSE。
2.3 日志插入(XLogInsert)
事务执行修改操作(插入/删除/提交等)时调用,向日志写入记录,流程如下:
-
备份块判断:无需备份则记录内存头信息与磁盘数据;需备份则记录文件号、块号及磁盘块备份;
-
缓冲区检查:根据
LogwrtRqst与LogwrtResult,若缓冲区使用量超过一半,触发刷盘; -
写入日志:通过
XLogCtl的Insert字段定位插入点,写入日志头部(确保不跨页)及主体数据; -
返回地址:返回下一条日志的插入地址。
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种检查点类型,流程如下:
-
检查点类型:
-
CHECKPOINT_IS_SHUTDOWN:数据库关闭时创建; -
CHECKPOINT_IMMEDIATE:快速刷新日志(紧急场景使用); -
CHECKPOINT_END_OF_RECOVERY:恢复完成后创建; -
CHECKPOINT_FORCE:强制创建(即使无日志)。
-
-
核心流程:
-
更新控制文件(关闭/恢复类检查点);
-
跳过重复检查点(与前一个检查点位置一致时);
-
等待所有事务提交完成;
-
更新全局缓存、控制文件、MULTIXACT信息;
-
调用
CheckPointGuts刷盘所有日志与缓冲区; -
写入检查点日志并刷盘,更新控制文件与检查点位置。
-
2.11 恢复检查点创建(RestartPoint)
恢复过程中创建的检查点,用于应对恢复阶段的系统崩溃,创建条件:
-
仅在恢复过程中创建;
-
恢复位置未包含在已有检查点中时创建。
3. XLOG日志恢复策略
系统崩溃重启后,通过StartupXlog触发恢复,核心逻辑:读取控制文件→定位最新检查点→执行REDO操作→初始化日志模块,具体流程如下:
3.1 恢复触发条件
满足以下任一条件,触发恢复流程:
-
日志目录中存在
backup_label文件(备份恢复标识); -
控制文件记录的最新检查点无法读取到对应日志;
-
控制文件记录的检查点与日志中检查点的REDO位置不一致。
3.2 恢复核心流程
-
更新控制信息到
ControlFile; -
初始化恢复用资源管理器;
-
从检查点REDO位置开始,逐条读取后续日志;
-
按资源管理器ID匹配对应模块,执行REDO操作(重复日志记录的原操作);
-
重复步骤3~4,直至无更多日志记录;
-
重建检查点,初始化
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数据库稳定运行的关键组件。
更多推荐
所有评论(0)