2. PostgreSQL物理存储与WAL机制
理解 PostgreSQL 的备份恢复能力,必须先看清它在磁盘上到底“长什么样”。PostgreSQL 的持久化模型可以概括为一句话:数据页是主体,WAL 是变更的账本,检查点是账本与主体对齐的锚点,LSN 是贯穿两者的时间坐标。本节围绕这条主线,把数据目录、表空间、数据文件、WAL、检查点、LSN 与恢复基础串成一个可观察、可验证的整体。
一、数据目录:集群的物理边界
通过 initdb 初始化的一个数据目录(PGDATA)就是一个数据库集群。它包含全局对象、每个数据库的子目录、WAL、事务状态、配置与运行标识文件。常用观察命令如下:
# 查看数据目录位置与关键子目录
psql -c "SHOW data_directory;"
ls -1 $PGDATA
# base/ global/ pg_wal/ pg_xact/ pg_tblspc/ postgresql.conf PG_VERSION postmaster.pid
其中 base/ 按数据库 OID 存放各库的物理文件,global/ 存放跨库共享的系统表,pg_wal/ 是 WAL 段文件目录,pg_xact/ 保存事务提交状态(CLOG),pg_tblspc/ 是表空间的挂载入口。数据库 OID 与目录名的对应关系可直接查询:
SELECT oid, datname FROM pg_database ORDER BY oid;
二、表空间与数据文件:逻辑对象如何落到磁盘
表空间是“逻辑对象到物理路径”的映射层。默认表空间 pg_default 指向 base/,pg_global 指向 global/;自定义表空间在 pg_tblspc/ 下以表空间 OID 建立符号链接,指向真实目录。创建与查看:
CREATE TABLESPACE ts_app LOCATION '/data/pg/ts_app';
SELECT spcname, oid, pg_tablespace_location(oid) FROM pg_tablespace;
表的数据文件命名规则是 relfilenode,而不是表名。表名到物理文件的链路为:pg_class.relname → relfilenode → 表空间路径/数据库OID/relfilenode。定位某个表的物理文件:
SELECT pg_relation_filepath('public.orders');
-- 输出示例:pg_tblspc/16385/PG_16_202307071/16386/24576
数据文件默认按 1GB 切分为多个段(24576、24576.1、24576.2……),内部以 8KB 页(page)为单位组织。页头包含 pd_lsn,记录“该页最后一次被修改时所对应的 WAL 位置”,这正是恢复时判断页面是否需要重放的关键依据。
三、WAL 日志:先写日志,再写数据
PostgreSQL 遵循 WAL(Write-Ahead Logging)原则:任何数据页修改在落盘前,描述该修改的 WAL 记录必须先持久化。WAL 段文件默认 16MB,命名如 000000010000000000000001,由时间线 ID、逻辑日志号、段号三部分组成。查看当前写入位置:
SELECT pg_current_wal_lsn(), pg_walfile_name(pg_current_wal_lsn());
-- 示例:0/1A2B3C48 | 000000010000000000000001
WAL 记录包含:事务 ID、操作类型(INSERT/UPDATE/DELETE/COMMIT)、目标数据块的块号与偏移、修改前后的元组信息,以及本条记录自身的 LSN。恢复时按 LSN 顺序重放,即可把数据文件从旧状态推进到一致状态。
四、检查点、LSN 与恢复基础
检查点(CHECKPOINT)做两件事:把共享缓冲区中的脏页刷到数据文件,并在 WAL 中写入一条检查点记录,标记“此 LSN 之前的所有修改已落盘”。因此恢复只需从最近一次检查点的 redo 位置开始重放 WAL,而不必从集群创建之初重放。相关观察命令:
CHECKPOINT;
SELECT pg_control_checkpoint(); -- 查看 checkpoint_lsn、redo_lsn、timeline_id
SELECT pg_current_wal_insert_lsn(), pg_current_wal_flush_lsn();
LSN(Log Sequence Number)是一个 64 位、单调递增的字节偏移量,格式为 X/Y,例如 0/1A2B3C48。它是 WAL 与数据页之间的公共坐标:数据页头记录 pd_lsn,WAL 记录自身携带 LSN,检查点记录 redo LSN。恢复的本质就是:从 redo LSN 开始,按 LSN 递增顺序重放 WAL,直到达到目标恢复点。
pd_lsn 与 WAL 记录的 LSN:若 pd_lsn < wal_lsn,说明该页落后于日志,需要应用这条 WAL 记录;否则可跳过。五、把机制串起来:一次写入的完整路径
以 UPDATE orders SET status='paid' WHERE id=1001; 为例,物理层面依次发生:
- 在共享缓冲区找到目标页,若不在则从数据文件读入。
- 生成 WAL 记录,写入 WAL 缓冲区,记录本次修改的 LSN。
- 事务提交时,WAL 缓冲区刷盘(
fsync),提交才算持久。 - 数据页在缓冲区中被标记为脏页,页头
pd_lsn更新为本次 LSN。 - 检查点到来时,脏页被刷入数据文件,WAL 中写入检查点记录。
崩溃恢复时,系统读取控制文件中的检查点信息,从 redo_lsn 开始扫描 WAL,对每条记录检查目标页的 pd_lsn,决定是否重放,直到 WAL 末尾,再进入一致性状态。这就是物理备份、PITR(时间点恢复)和流复制共同的底层基础。
六、关键参数与观察点
与本节机制直接相关的参数包括:wal_level(决定 WAL 记录详细程度,归档与复制至少为 replica)、wal_segment_size(默认 16MB)、checkpoint_timeout 与 max_wal_size(控制检查点频率)、full_page_writes(崩溃后页撕裂保护)。日常巡检可关注:
SELECT pg_current_wal_lsn() AS cur_lsn,
pg_walfile_name(pg_current_wal_lsn()) AS cur_seg,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) AS wal_bytes;
SELECT redo_lsn, checkpoint_lsn, timeline_id FROM pg_control_checkpoint();
当 pg_wal/ 持续增长、检查点频繁触发、或 pg_control_checkpoint() 中 redo_lsn 与当前 LSN 差距过大时,说明 WAL 生成速度与检查点刷盘速度失衡,需要调整检查点参数或排查长事务。这些观察点正是后续物理备份、PITR 与流复制章节的起点。