Skip to content

物理备份原理 ​

物理备份(physical backup)指的是直接复制数据库集簇的物理文件,包括数据目录下的数据文件、WAL、控制文件等。它与逻辑备份的根本区别在于:逻辑备份导出的是「重建数据的 SQL 或数据流」,而物理备份拷贝的是「文件本身的字节」。

这个区别决定了两者在能力上的分工。物理备份速度快(顺序拷贝文件,不需要读出行并序列化)、恢复快(文件回填 + WAL 重放)、可以支撑 PITR(时间点恢复);代价是备份体积大(含索引、空闲空间、WAL)、与 PostgreSQL 主版本强绑定、无法只恢复单表。换言之,逻辑备份负责「灵活」,物理备份负责「保命」。

理解物理备份原理的关键在于回答一个问题:为什么不能简单地 cp -r 数据目录? 因为数据库在运行中,文件随时在被修改——页被部分写入、WAL 在被追加、检查点会重写 pg_control。一个「正在被写」的目录,拷贝出来的结果没有任何一致性保证。物理备份的全部机制,都是为了解决这个一致性问题。

本页讲清物理备份的底层原理:数据目录里有什么、备份为什么要分两阶段、backup_label 与 backup_manifest 是什么、WAL 在备份中扮演什么角色。后续几页再分别展开 pg_basebackup、增量备份与文件系统级快照。

基础概念 ​

什么是物理备份 ​

物理备份是对整个数据库集簇(cluster)的物理拷贝。它包含:

  • 主数据目录($PGDATA)下的全部数据文件;
  • 各表空间目录(如果使用了 pg_tblspc/ 下的独立表空间);
  • 足够让数据库回到一致状态的 WAL。

物理备份的粒度是集簇级,不能只备份其中某个数据库或某张表——这是它与 pg_dump 最直观的差异。

物理备份 vs 逻辑备份 ​

维度逻辑备份(pg_dump)物理备份
备份对象对象定义 + 行数据文件字节
备份粒度库 / 模式 / 表整个集簇
备份速度慢(要读出并序列化所有行)快(顺序拷贝文件)
恢复速度慢(重建索引、重放 SQL)快(文件回填 + WAL 重放)
备份体积较小(去掉了索引与膨胀空间)较大(含索引、空闲空间、WAL)
能否 PITR不能能
跨大版本可以不可以
跨平台可以一般不可以(同平台、同字节序)
是否可读是(SQL 文本 / 结构化归档)否(二进制文件)
主要工具pg_dump / pg_dumpall / pg_restorepg_basebackup / 低层 API / 快照

两者不是替代关系,而是互补:生产环境通常同时保留逻辑备份(用于迁移、单表找回、审计归档)与物理备份(用于灾难恢复与 PITR)。

一致性:物理备份的核心问题 ​

数据库运行期间,文件在持续变化。想象在没有任何协调的情况下直接拷贝数据目录,会遇到以下问题:

  • 页的撕裂写(partial page write):一个 8KB 页只写了前 4KB 就被拷贝走,得到一个「半新半旧」的页,这在逻辑上是错误的;
  • 检查点与 pg_control 不同步:数据文件拷贝到一半时,检查点发生了,pg_control 里的检查点位置指向新的状态,而有些文件还是旧的;
  • WAL 不完整:拷贝期间产生的 WAL 段没被拷进去,恢复时无处可查;
  • 文件级不一致:表与索引的拷贝时刻不同,彼此对不上。

物理备份机制要做的事,就是让「拷贝出来的文件集合」在某一个 LSN 上是一致(consistent)的。这个「某一个 LSN」就是备份的起点。

为什么 full_page_writes 是关键

PostgreSQL 默认 full_page_writes = on。它保证「检查点之后第一次修改某个页」时,把整个页的镜像写进 WAL。这样即使磁盘上发生了撕裂写,恢复时也能用 WAL 里的完整页镜像覆盖掉坏页。这是物理备份在文件级不一致的情况下依然能恢复到一致状态的底层保证。

备份必须包含的组成部分 ​

一个可用的物理备份(基础备份,base backup)必须同时具备:

组成部分内容作用
数据文件base/、global/ 等目录下的文件数据本身
表空间文件pg_tblspc/ 指向的目录非默认表空间的数据
pg_control控制文件集簇元信息与检查点位置
起始 WAL 位置通常记录在 backup_label恢复时从何处开始重放
备份期间的 WAL备份过程中产生的 WAL 段补齐从备份开始到结束的变化
backup_manifest备份清单(新版本默认生成)校验备份完整性

其中备份期间的 WAL 是最容易被忽略的一项。基础备份在时间上是有跨度的(可能几分钟到几小时),这段时间里数据库还在写入,这些写入只存在于 WAL 里,因此必须一并保存。

低层 API 的两个阶段 ​

PostgreSQL 提供了一对系统函数来「显式声明一段备份窗口」:

函数作用引入 / 更名
pg_backup_start(label [, fast])进入备份模式,返回起始 LSN(pg_lsn)PG15 起由 pg_start_backup 更名
pg_backup_stop([wait_for_archive])退出备份模式,返回 lsn、labelfile、spcmapfilePG15 起由 pg_stop_backup 更名
sql
-- 进入备份模式(注意:不是事务内的临时操作)
SELECT pg_backup_start('nightly_20260930');

-- 此时可以拷贝数据目录 / 打存储快照

-- 退出备份模式
SELECT * FROM pg_backup_stop();

这对函数构成的「开始 → 拷贝 → 结束」三段式,是文件系统级快照备份与自研备份工具的基础。

旧名已更名

PostgreSQL 15 起,pg_start_backup() 改名为 pg_backup_start(),pg_stop_backup() 改名为 pg_backup_stop()。旧名作为保留别名仍然可用,但新代码与新文档应统一使用新名。看到老教程里的 pg_start_backup,要知道它就是今天的 pg_backup_start。

获取基础备份的三种方式 ​

方式依赖一致性由谁保证适用场景
pg_basebackup复制协议工具自动处理最常用、最省心
低层 API + 手工拷贝pg_backup_start/stop使用者自己自研备份、特殊存储
存储快照低层 API + LVM/ZFS/EBS快照原子性超大库、秒级备份

生产上绝大多数场景首选 pg_basebackup;超大库(TB 级)为压缩备份时长会用存储快照;需要把备份直接落到对象存储时会用 pgBackRest / Barman 这类工具(它们内部仍是这三种机制的封装)。

语法与用法 ​

pg_basebackup 最小用法 ​

bash
# 生成 plain 格式基础备份,包含所需的 WAL
pg_basebackup -D /backup/base_20260930 -Fp -Xs -P -c spread
text
    0/2290400 kB (0%), 0/1 tablespace (...)
   459028/1200520 kB (38%), 0/1 tablespace (...)
  1200520/1200520 kB (100%), 0/1 tablespace (...)

低层 API 用法 ​

sql
-- 1. 记录起始位置(会创建一个检查点)
SELECT pg_backup_start('lowlevel_20260930');

-- 2. 在另一个会话/脚本里拷贝数据目录(或打存储快照)

-- 3. 结束备份,返回 lsn / labelfile / spcmapfile
SELECT lsn, labelfile, spcmapfile FROM pg_backup_stop();

查看备份进度与状态 ​

sql
-- 备份进行中时,可观察服务端的进度
SELECT phase, backup_total, backup_streamed, tablespaces_total, tablespaces_streamed
FROM pg_stat_progress_basebackup;
text
         phase          | backup_total | backup_streamed | tablespaces_total | tablespaces_streamed
------------------------+--------------+-----------------+-------------------+----------------------
 streaming database files |    1200520 kB |       459028 kB |                 1 |                    0
(1 row)
sql
-- 确认当前是否处于备份模式
SELECT pg_is_in_backup(), pg_backup_start_time();
text
 pg_is_in_backup |    pg_backup_start_time
-----------------+-----------------------------
 t               | 2026-09-30 02:00:03.412+08
(1 row)

校验备份完整性 ​

bash
# 用备份自带的 manifest 校验文件与校验和
pg_verifybackup /backup/base_20260930
text
backup successfully verified

详细说明 ​

数据目录的物理布局 ​

理解物理备份,先要知道被拷贝的东西长什么样。一个典型的数据目录结构:

text
$PGDATA/
├── PG_VERSION          # 主版本号,纯文本
├── base/               # 各数据库的数据文件(按数据库 OID 分目录)
│   └── 16384/          # 单个数据库目录
│       ├── 1259          # 系统表 / 用户表的数据文件(按 relfilenode 命名)
│       └── 16385
├── global/             # 集簇级共享对象(pg_database、pg_authid 等)
├── pg_control          # 控制文件(二进制)
├── pg_wal/             # WAL 段文件、归档状态
│   ├── 000000010000000000000001
│   └── archive_status/
├── pg_xact/            # 事务提交状态(CLOG)
├── pg_multixact/       # 多事务状态
├── pg_subtrans/        # 子事务状态
├── pg_tblspc/          # 表空间符号链接
├── pg_logical/         # 逻辑解码状态
├── pg_replslot/         # 复制槽状态
├── pg_stat/            # 统计信息
└── postgresql.conf     # 配置文件

物理备份要覆盖的核心是 base/、global/、pg_control、pg_wal/ 与 pg_tblspc/ 指向的内容。pg_xact/、pg_multixact/ 等状态目录同样重要——它们承载事务可见性信息,缺了就无法确定「哪些行已提交」。

表空间是备份的隐藏陷阱

使用独立表空间时,数据实际存放在 pg_tblspc/<oid> 符号链接指向的目录里,而不在主数据目录内。很多「手工 cp 备份」事故就发生在这一步:只拷了 $PGDATA,没拷表空间目录,恢复后发现某些表不存在。

一致性从哪里来:检查点 ​

pg_backup_start() 的第一步是强制执行一次检查点(checkpoint)。这一步不是可有可无的优化,而是一致性的起点:检查点把所有脏页刷到磁盘,并在 pg_control 中记录一个「已知一致」的位置。

检查点的作用有两点:

  1. 它确定了恢复时从哪里开始重放 WAL——重放只需从检查点位置开始,不必从日志起点开始;
  2. 它把「拷贝开始时数据文件的状态」固定下来,让备份窗口内的文件变化都能被 WAL 覆盖。

pg_backup_start 的第二个可选参数 fast(布尔值,默认 false)控制检查点是否「立即完成」。默认的 spread 模式会尽量摊薄检查点的 I/O 冲击,让检查点尽快完成但不过度影响在线业务;fast = true 则强制立即完成,适合停机窗口或对备份时长敏感的场景。

sql
-- 默认:摊薄式检查点,对在线业务更友好
SELECT pg_backup_start('normal');

-- 立即式检查点,备份开始更快但 I/O 冲击更大
SELECT pg_backup_start('urgent', true);

备份窗口内的文件变化 ​

备份窗口(从 pg_backup_start 到 pg_backup_stop)内,数据文件仍在被修改。这时候「拷贝文件」这件事本身就需要约定:

  • 被修改的页由 full_page_writes 兜底:如果拷贝到的是一个被撕裂的页,恢复时 WAL 里的完整页镜像会覆盖它;
  • 新创建的文件如果在拷贝扫描到它之前就存在,会被拷走;如果数据文件列表是「先扫描再拷贝」,拷贝期间新建的文件可能被漏掉——这正是 pg_basebackup 通过服务端统一 WAL 位置来避免的问题;
  • 被删除的文件同理,需要考虑「删除发生在拷贝前还是拷贝后」。

因此一个可靠的物理备份实现,必须确保「文件集合的扫描时刻」与「WAL 起始位置」是配套的。pg_basebackup 通过复制协议在服务端做这件事;自研方案则必须严格保证先 pg_backup_start 拿到起始 LSN,再扫描文件。

backup_label 文件 ​

pg_backup_stop() 返回的 labelfile 字段,就是备份标签文件(backup_label)的内容。它记录了这次备份的元信息:

text
START WAL LOCATION: 0/2000028 (file 000000010000000000000002)
CHECKPOINT LOCATION: 0/2000060
BACKUP METHOD: streamed
BACKUP FROM: primary
START TIME: 2026-09-30 02:00:03 CST
LABEL: nightly_20260930
START TIMELINE: 1

各字段含义:

字段含义
START WAL LOCATION备份起点 LSN 及其所在 WAL 段
CHECKPOINT LOCATION起始检查点的 WAL 位置,恢复时从这里开始重放
BACKUP METHOD备份方式(streamed / pg_basebackup 等)
BACKUP FROM备份来源是主库还是备库(primary / standby)
START TIME备份开始时间
LABEL用户指定的标签
START TIMELINE起始时间线

这个文件必须被写入恢复目标的数据目录,恢复才能正确启动。pg_basebackup 会自动帮你写好;用低层 API 时必须自己把 labelfile 的输出保存为数据目录下的 backup_label。

备份窗口的会话约束

pg_backup_start 与 pg_backup_stop 必须在同一个会话中依次调用(服务端按会话跟踪备份状态)。如果会话中途断开,备份状态会随之失效,需要重新开始。这就是为什么用低层 API 时通常用 psql 保持一个长连接,或者用 psql -c 配合脚本严格串行。

tablespace_map 文件 ​

如果集簇使用了默认表空间之外的独立表空间,pg_backup_stop() 的 spcmapfile 字段会给出 tablespace_map 的内容,形如:

text
16385 /data/tblspc/reporting
16386 /data/tblspc/archive

每行是「表空间 OID + 原始绝对路径」的映射。恢复时,PostgreSQL 依据这个文件在主数据目录的 pg_tblspc/ 下重建符号链接。如果没有使用额外表空间,该字段通常为空。

它的意义在于:表空间的物理路径是备份的一部分。把备份恢复到不同的机器或不同的目录结构时,需要按实际环境调整这些路径(pg_basebackup 的 -T olddir=newdir 就是做这件事的)。

backup_manifest 与 pg_verifybackup ​

较新版本的 pg_basebackup 与低层 API 会生成一个 backup_manifest 文件,里面逐条记录了备份中每个文件的信息:

记录内容说明
文件路径相对于数据目录的路径
文件大小字节数
修改时间文件 mtime
校验和CRC32C(或 SHA)校验和

pg_verifybackup 就是依据这份清单做校验的工具。它的校验分几个阶段:

  1. 读取并解析 backup_manifest;
  2. 比对备份目录中的文件列表与 manifest 是否一致(有没有缺失、有没有多余);
  3. 重新计算每个文件的校验和,与 manifest 对比;
  4. 用 pg_waldump 解析备份携带的 WAL,确认 WAL 的完整性。
bash
# 完整校验(含 WAL 解析)
pg_verifybackup /backup/base_20260930

# 只校验文件与校验和,跳过 WAL 解析
pg_verifybackup --no-parse-wal /backup/base_20260930

# 若备份的 manifest 不完整(例如从备库备份),可放宽检查
pg_verifybackup --skip-checksums /backup/base_20260930

它解决的是「备份任务成功 ≠ 备份可用」这个经典问题:文件被写出来了,不代表它完整、没被截断、没在传输中损坏。

WAL 在备份中的三种处理方式 ​

基础备份与 WAL 的关系有三种处理策略,对应 pg_basebackup -X 的三个取值:

方式-X 取值做法优缺点
不含 WALnone只拷数据文件备份体积最小,但恢复必须依赖外部 WAL 归档
备份结束时收集fetch备份结束后从服务端拉取所需 WAL 段需要 wal_keep_size 足够大,否则 WAL 可能已被回收
并行流式获取stream备份期间同步流式接收 WAL默认方式,最可靠;需要两个复制连接
bash
# 默认:流式(需要 max_wal_senders 至少多留一个)
pg_basebackup -D /backup/base1 -Xs

# 结束时收集(注意 wal_keep_size 要足够)
pg_basebackup -D /backup/base2 -Xf

# 不含 WAL,恢复完全依赖归档
pg_basebackup -D /backup/base3 -Xn

-X fetch 的风险在于:如果备份耗时很长,而 wal_keep_size 设置过小,备份开始时的 WAL 段可能在备份结束前就被回收了,这时备份会失败。-X stream 从开始就持续接收 WAL,不存在这个窗口,因此是默认值。

-X none 不是「不要 WAL」

-Xn 表示备份包里不携带 WAL,但这只在你另有可靠的 WAL 归档链路时才能用。如果既没有归档、又没有携带 WAL,那么这个基础备份根本不能恢复——它缺少从备份起点开始的所有变化记录。

备份期间的 I/O 与性能影响 ​

物理备份是重 I/O 操作。它的开销来自三处:

  1. 起始检查点:pg_backup_start 触发检查点,会把累积的脏页全部刷盘,产生一波写 I/O 尖峰;
  2. 文件读取:备份进程顺序读取整个数据目录,与正常查询形成 I/O 竞争;
  3. WAL 流量:-X stream 会额外占用一个 walsender 与网络带宽。

缓解手段:

手段说明
-c spread使用摊薄式检查点(默认),避免一次性的 I/O 尖峰
-r rate限制从服务端读取数据的速率(pg_basebackup)
--compress压缩传输(客户端或服务端),降低带宽占用,增加 CPU 开销
在备库上备份把备份的 I/O 从主库转移到备库
使用存储快照秒级完成,几乎不影响在线业务

pg_basebackup 的压缩参数写法为 -Z [{client|server}-]method[:detail],方法可取 gzip / lz4 / zstd / none,detail 可以是压缩等级或 level=/workers= 等关键字组合。

bash
# 服务端压缩,减少网络带宽;客户端解压回填
pg_basebackup -D /backup/base_zstd -Fp -Xs --compress=server-zstd:level=3

# tar 格式 + 客户端 gzip(文件名自动加 .gz)
pg_basebackup -D /backup/base_tar -Ft -Xs -z

从备库做基础备份 ​

pg_basebackup 不只能从主库做备份,也可以从备库做——这对降低主库压力很有价值。但备库备份有几个限制:

  • 备份中不会生成备份历史文件(backup history file);
  • 备份结束时无法强制备库切换 WAL 段;若使用 -X none 且主库写入很低,可能需要等待很久,此时可在主库上执行 pg_switch_wal() 主动触发切换;
  • 如果备库在备份期间被提升为主库,备份会失败;
  • 主库必须开启 full_page_writes,以保证备份所需的 WAL 记录包含完整的页镜像。
bash
# 从备库(10.0.0.12:5432)做基础备份
pg_basebackup -h 10.0.0.12 -p 5432 -U replicator \
  -D /backup/base_from_standby -Fp -Xs -P

物理备份与 PITR 的关系 ​

物理备份本身只能恢复到「备份结束那一刻」。真正让它可以恢复到任意时间点的,是从备份起点开始连续的 WAL 归档。

text
时间轴 ───────────────────────────────────────────────────────►

[基础备份 B1]───────────────────────┐
  起点 LSN 0/2000028                │
                                    │  归档 WAL(持续)
              ┌─────────────────────┴──────────────────────┐
              │  从 0/2000060 开始重放,可停在任意 LSN/时间  │
              └────────────────────────────────────────────┘

这也解释了「为什么基础备份要定期重做」:基础备份越旧,恢复时需要重放的 WAL 越多,RTO 越长;反之,基础备份越新,需要保存的 WAL 归档越少,RPO 窗口越短。

完整链路 = 基础备份 + 连续归档

只有「一份基础备份 + 从它起点开始的全部 WAL」这套组合,才是完整的物理备份体系。只做基础备份,恢复点就被钉死在备份时刻;只做归档而不做基础备份,则没有恢复的起点。

文件系统级快照的定位 ​

存储快照(LVM / ZFS / 云盘快照)是第三种获取基础备份的方式:先调用 pg_backup_start 进入备份模式,然后对数据卷打一个快照(原子、秒级),最后调用 pg_backup_stop 结束。

它的优势是快——对一个 TB 级数据库,秒级完成,几乎不影响在线业务;代价是依赖存储能力,且快照本身仍在原存储上(不是独立副本),还需要额外地「把快照传输/导出到独立介质」才算真正的备份。

具体机制(COW 写时复制、fsfreeze、zfs send/recv、lvconvert --merge)会在《文件系统级备份》一页展开。

物理备份的不可用场景 ​

有几类需求物理备份做不了,必须用逻辑方式:

需求物理备份替代方案
只恢复一张表不支持(集簇级)pg_dump / 单表逻辑导出
跨大版本升级不支持pg_dump/pg_restore、pg_upgrade
跨操作系统/字节序迁移一般不支持逻辑方式
缩短归档体积(去掉膨胀)不支持pg_dump + 重建
部分数据脱敏后给测试不支持pg_dump + --filter

把物理备份当万金油,会在「只丢了一张表」这种小故障上付出整实例恢复的代价。

备份与恢复的对称性 ​

物理备份体系里有一个容易被忽略的原则:备份怎么做的,就决定了恢复只能怎么做。

  • 用 -X none 做的备份,恢复时必须能从归档拿到 WAL;
  • 用 -Ft 做的 tar 备份,恢复时要先解包;
  • 用表空间映射做的备份,恢复到不同目录时要还原映射关系;
  • 用低层 API 做的备份,backup_label 与 tablespace_map 要手工放回数据目录。

因此备份策略的设计必须同时考虑恢复流程。只考虑「怎么备」,不考虑「怎么恢复」的备份方案,往往在真正需要时会卡住。

实战示例 ​

示例一:一次标准的 pg_basebackup 基础备份 ​

bash
# 准备目录
mkdir -p /backup/base_20260930

# 执行备份:plain 格式、流式 WAL、显示进度、摊薄式检查点
pg_basebackup \
  -h 127.0.0.1 -p 5432 -U replicator \
  -D /backup/base_20260930 \
  -Fp -Xs -P -c spread \
  -l nightly_20260930

# 校验
pg_verifybackup /backup/base_20260930
text
    0/2290400 kB (0%), 0/1 tablespace (...)
  1200520/1200520 kB (100%), 0/1 tablespace (...)
backup successfully verified

示例二:用低层 API 备份并保存标签 ​

bash
# 1) 在会话中开始备份,并保存返回的起始 LSN
psql -Atc "SELECT pg_backup_start('manual_20260930');"
text
0/2000028
bash
# 2) 拷贝数据目录(示例:tar 打包;实际生产建议用快照或工具)
tar -C /var/lib/postgresql/18/main -cf /backup/data_20260930.tar .

# 3) 结束备份:一次查询取出三个字段,再按需拆分保存
psql -Atc "SELECT lsn, labelfile, spcmapfile FROM pg_backup_stop();" \
> /backup/stop_output.txt

# 人工核对输出后,把第二、三个字段分别保存为文件
text
0/2290400|START WAL LOCATION: 0/2000028 (file 000000010000000000000002)
CHECKPOINT LOCATION: 0/2000060
BACKUP METHOD: pg_basebackup
...|16385 /data/tblspc/reporting

不要重复调用 pg_backup_stop

pg_backup_stop() 每调用一次就会结束当前备份窗口,第二次调用会报 backup is not in progress。因此必须用一次查询取出全部三个字段(SELECT lsn, labelfile, spcmapfile FROM pg_backup_stop();),再把结果拆分保存;不要分三次分别查询。

示例三:观察备份进度 ​

sql
-- 在另一个会话里持续观察
SELECT
    phase,
    backup_total,
    backup_streamed,
    pg_size_pretty(backup_streamed) AS streamed,
    tablespaces_total,
    tablespaces_streamed
FROM pg_stat_progress_basebackup;
text
          phase           | backup_total | backup_streamed | streamed | tablespaces_total | tablespaces_streamed
--------------------------+--------------+-----------------+----------+-------------------+----------------------
 streaming database files |      1200520 |          459028 | 448 MB   |                 1 |                    0
(1 row)

phase 会依次经过 initializing → waiting for checkpoint to finish → estimating backup size → streaming database files → transferring wal files。

示例四:确认 wal_level 与复制连接是否就绪 ​

sql
SELECT name, setting FROM pg_settings
WHERE name IN ('wal_level', 'max_wal_senders', 'wal_keep_size', 'full_page_writes');
text
       name        | setting
-------------------+---------
 full_page_writes  | on
 max_wal_senders   | 10
 wal_keep_size     | 512MB
 wal_level         | replica
(4 rows)
bash
# 确认复制连接可用(会停在等待 WAL 的位置,Ctrl-C 退出)
pg_receivewal -h 127.0.0.1 -U replicator -D /tmp/waltest --no-loop

示例五:从基础备份恢复到临时实例验证 ​

bash
# 1) 准备恢复目录
mkdir -p /var/lib/postgresql/18/verify
tar -C /var/lib/postgresql/18/verify -xf /backup/data_20260930.tar

# 2) 放回标签文件
cp /backup/backup_label /var/lib/postgresql/18/verify/backup_label

# 3) 声明为备库(或 PITR 恢复)
touch /var/lib/postgresql/18/verify/standby.signal
conf
# /var/lib/postgresql/18/verify/postgresql.auto.conf
restore_command = 'cp /mnt/wal_archive/%f %p'
bash
# 4) 用不同端口启动,验证能进入一致状态且数据可查
pg_ctl -D /var/lib/postgresql/18/verify \
       -o "-p 5433" start

psql -p 5433 -c "SELECT pg_is_in_recovery(), count(*) FROM orders;"
text
 pg_is_in_recovery | count
-------------------+-------
 t                 | 48213
(1 row)

这一步是整个备份体系的「体检」:能启动、能进入一致状态、能查到数据,才说明这份基础备份是活的。

注意事项 ​

备份成功不等于备份可用。 备份工具返回 0 只说明文件写出来了,不代表文件完整、可解压、能被服务端启动。请务必把 pg_verifybackup 与恢复演练纳入标准流程。

物理备份与主版本强绑定。 一份 PG 18 的基础备份无法被 PG 17 的实例启动,也无法用于跨大版本升级。跨版本要另走逻辑路线。

平台与字节序要一致。 物理文件与架构相关(大小端、页布局、对齐)。同平台同架构的原位恢复最稳,跨平台恢复通常不可行。

表空间必须一起备份。 只拷 $PGDATA 而丢掉 pg_tblspc/ 指向的目录,是最常见的「备份残缺」事故。

备份窗口内不要做 DDL 之外的危险操作。 备份进行时,pg_backup_start/stop 所处的会话不能中断;同时应避免在备份窗口内做 DROP DATABASE、表空间增删等结构性变更。

-X none 只在有归档时可用。 不含 WAL 的备份包必须配合连续的 WAL 归档才有恢复能力,否则它只是一个「能启动到备份时刻」的快照。

备份期间的 WAL 段不能被回收。 使用 -X fetch 时,wal_keep_size 必须足以覆盖备份时长;使用流复制槽时要监控槽的 WAL 保留量,避免磁盘被撑满。

不要在备份目录上做增量文件同步作为「增量备份」。 用 rsync 对数据目录做差异同步,得到的是一堆彼此不一致的文件快照,不具备任何一致性保证。增量备份必须在 PostgreSQL 的备份机制内做(PG17 原生增量或第三方工具)。

备份要落到与生产不同的存储。 备份文件与数据目录在同一块磁盘或同一台机器上,就失去了「灾难恢复」的意义。至少要跨机器,重要数据要跨机房。

恢复演练必须定期做。 未经验证的备份等于没有备份。演练要覆盖「完整恢复」与「PITR 到指定时间」两类流程,并记录耗时(RTO)。

最佳实践 ​

  1. 物理备份与逻辑备份并行保留。 物理备份负责灾难恢复与 PITR,逻辑备份负责迁移、单表找回与审计归档。二者的保留周期与存储位置分别设计:
bash
# 物理:每日基础备份 + 持续 WAL 归档
pg_basebackup -D /backup/base_$(date +%F) -Fp -Xs -P

# 逻辑:每周全库 + 每日关键库
pg_dump -Fc -f /backup/logic_$(date +%F).dump mydb
  1. 基础备份与 WAL 归档视为一个整体。 只做基础备份,恢复点被钉死在备份时刻;只做归档,没有恢复起点。两者的监控与保留策略必须配套。

  2. 每次备份后自动跑 pg_verifybackup。 把校验作为备份任务的最后一步,校验失败立即告警,而不是等到恢复时才发现备份损坏:

bash
pg_basebackup -D /backup/base_latest -Fp -Xs -P \
  && pg_verifybackup /backup/base_latest
  1. 用摊薄式检查点,避免备份引发 I/O 尖峰。 pg_basebackup 默认即为 -c spread;手工调用低层 API 时也推荐先用默认(fast = false),仅在停机窗口才用 fast = true。

  2. 对备份进程做速率限制。 用 -r 限制从服务端读取的速率,或对客户端做 cgroup/ionice 限制,避免备份挤占在线业务的 I/O:

bash
pg_basebackup -D /backup/base_latest -Fp -Xs -P -r 100M
  1. 大库把备份做在备库上。 用备库承担备份的文件读取开销,避免影响主库。注意备库备份不生成备份历史文件,且主库必须开启 full_page_writes。

  2. TB 级库优先考虑存储快照。 用 pg_backup_start + 快照 + pg_backup_stop 把备份窗口从小时级压缩到秒级,再把快照异步导出到独立存储:

sql
SELECT pg_backup_start('snapshot_20260930');
-- 打快照 ...
SELECT * FROM pg_backup_stop();
  1. 备份命名带可检索的时间与用途。 目录名或标签里带上日期、类型(full / incremental)、用途(nightly / pre-upgrade),便于保留策略与排障:
bash
pg_basebackup -D /backup/full_20260930_0200 -l "full_20260930_0200" -Fp -Xs
  1. 对 pg_wal 目录大小与归档状态设告警。 pg_wal 持续增长通常意味着归档失败;备份任务本身也会短暂占用 WAL 保留空间。两者都应及时发现。

  2. 记录并定期复核 RTO / RPO。 每次恢复演练都记录「从开始到可用」的耗时与「丢失的数据量」,用真实数字验证备份策略是否满足业务要求,而不是停留在假设。

  3. 备份脚本必须幂等且可重入。 备份中断后重跑不应破坏已有备份;使用带时间戳的新目录而不是覆盖固定目录,避免「写完一半」的目录被误认为完整备份。

  4. 把恢复流程写成可执行的 Runbook 或脚本。 恢复时的操作顺序(停服务、保全现场、回填数据、放回 backup_label、写恢复配置、启动、验证)固定下来,减少事故中的判断与犹豫。

下一节

继续学习:pg_basebackup

参考链接 ​