数据库基础体系 · 第 101/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

PostgreSQL 备份恢复:pg_dump、Base Backup、WAL 归档和 PITR

1. 先区分四个概念

PostgreSQL 的“备份”不是一种机制,而是几种层次不同的机制:

机制 备份对象 能否恢复单个数据库或表 能否恢复到任意时间点 是否依赖 WAL
pg_dump 数据库的逻辑对象和数据 可以 不可以 不依赖归档 WAL
pg_dumpall --globals-only 集群级角色、表空间等全局对象 不适用 不可以 不依赖归档 WAL
Base Backup 整个 PostgreSQL 集群的数据目录 通常不能直接只恢复一个表 配合 WAL 可以 恢复到备份之后需要
WAL 归档 数据变化的顺序日志 不能单独恢复 配合 Base Backup 可以 本身就是 WAL
PITR 恢复过程和目标 恢复的是整个集群状态 可以 必须有 Base Backup 和连续 WAL

这里的“集群”指一个 PostgreSQL server instance 管理的数据目录,而不是某个单独的数据库。一个 PostgreSQL 集群中可以有多个数据库;物理备份覆盖整个数据目录,逻辑备份则可以针对其中一个数据库。

最重要的关系是:

PITR=Base Backup+目标时刻之前连续的 WAL\text{PITR} = \text{Base Backup} + \text{目标时刻之前连续的 WAL}

而:

pg_dumpBase Backup\text{pg\_dump} \neq \text{Base Backup}

pg_dump 导出的是逻辑内容,Base Backup 复制的是物理数据目录。两者解决的问题不同,不能互相替代。


2. PostgreSQL 恢复为什么依赖 WAL

2.1 WAL 的基本语义

WAL,即 Write-Ahead Logging,是 PostgreSQL 的预写式日志。其核心约束是:

在数据页被写入持久存储之前,与该数据页变化对应的 WAL 必须已经持久化。

WAL 记录通常描述页面变化、事务提交、检查点等信息。事务提交时,提交记录被写入并持久化;数据页可能仍然留在共享缓冲区中,稍后才写入数据文件。

因此,崩溃恢复可以执行:

  1. 从某个检查点开始读取 WAL;
  2. 重放已经记录的页面变化;
  3. 根据提交、回滚和其他控制记录恢复到一致状态;
  4. 丢弃未完成事务产生的效果。

WAL 的本质不是“SQL 操作日志”。它是面向物理存储恢复的内部日志,通常不能直接从 WAL 还原出一组可移植的 SQL 语句。

2.2 Checkpoint 不是备份

Checkpoint 会建立一个恢复起点:

  • 将脏页逐步或集中写入数据文件;
  • 在 WAL 中写入检查点记录;
  • 让崩溃恢复不必从集群创建以来的第一条 WAL 开始重放。

但 checkpoint 不会:

  • 复制数据目录;
  • 把数据保存到独立备份介质;
  • 防止磁盘损坏;
  • 记录一个可以脱离原集群使用的完整副本。

因此,“最近刚做过 checkpoint”不能算作备份。

2.3 Base Backup 与 WAL 的关系

假设一次 Base Backup 的备份过程为:

备份开始:LSN 0/5000000
备份结束:LSN 0/9000000

备份目录中的数据文件来自不同时间点。某个数据页可能在备份开始前复制,另一个数据页可能在备份结束前复制。因此,Base Backup 的文件集合本身未必对应一个瞬间状态。

PostgreSQL 通过以下方式保证恢复一致性:

  1. Base Backup 记录备份开始位置和结束信息;
  2. 备份期间产生的 WAL 被保留;
  3. 恢复时从合适的起点开始重放 WAL;
  4. 重放到备份结束及之后的 WAL,使所有数据页达到一致状态。

形式化地说,设:

  • BB:Base Backup 的文件集合;
  • LbL_b:备份需要的 WAL 起始位置;
  • LtL_t:目标恢复位置;
  • W[Lb,Lt]W[L_b, L_t]:从 LbL_bLtL_t 的连续 WAL。

则恢复到目标位置的必要条件近似为:

BW[Lb,Lt]正确的 timelineB \land W[L_b, L_t] \land \text{正确的 timeline}

缺少任意一项都可能导致恢复失败或恢复不到目标状态。


3. pg_dump:逻辑备份

3.1 pg_dump 备份什么

pg_dump 连接到一个数据库,通过 SQL 和 PostgreSQL 协议读取数据库对象与数据,然后生成逻辑转储文件。它可以包含:

  • schema;
  • 表、索引、约束;
  • 数据;
  • 序列及其当前值;
  • 函数、触发器、视图;
  • 大对象;
  • 权限和对象所有者信息。

它不复制 PostgreSQL 数据目录中的物理页面,也不保存足以进行崩溃恢复的 WAL。

默认情况下,pg_dump 使用一致性快照读取数据库。备份期间其他事务可以继续提交;转储看到的是一个一致的逻辑视图,而不是逐表读取时刻的混合状态。

例如,创建一个示例数据库:

CREATE DATABASE appdb;

连接后创建表和数据:

CREATE TABLE orders (
    id         bigint PRIMARY KEY,
    customer   text NOT NULL,
    amount     numeric(12, 2) NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now()
);

INSERT INTO orders (id, customer, amount)
VALUES
    (1, 'alice', 100.00),
    (2, 'bob',   250.50);

使用自定义格式导出:

pg_dump \
  --dbname=appdb \
  --format=custom \
  --file=/backup/appdb.dump

--format=custom 生成 PostgreSQL 自定义归档格式,适合使用 pg_restore 选择性恢复和并行恢复。也可以使用:

pg_dump --dbname=appdb --format=directory --file=/backup/appdb.dir

目录格式将转储拆成多个文件,也支持并行恢复。

如果希望得到可读的 SQL:

pg_dump \
  --dbname=appdb \
  --format=plain \
  --file=/backup/appdb.sql

这类文件可以检查、修改后再执行,但大型数据库恢复时通常不如自定义格式灵活。

3.2 恢复 pg_dump 转储

自定义格式通常恢复到一个已经创建的目标数据库:

createdb appdb_restore

pg_restore \
  --dbname=appdb_restore \
  --exit-on-error \
  /backup/appdb.dump

createdb 使用的是当前连接用户。如果转储中包含原始对象所有者,而目标集群中没有对应角色,恢复可能出现:

ERROR: role "app_owner" does not exist

因此,迁移或灾备时还需要单独保存集群级对象:

pg_dumpall \
  --globals-only \
  > /backup/globals.sql

恢复顺序通常是:

psql --dbname=postgres --file=/backup/globals.sql
createdb appdb_restore
pg_restore --dbname=appdb_restore --exit-on-error /backup/appdb.dump

globals.sql 可能包含角色、角色成员关系和表空间定义。执行它需要足够的管理权限,并且其中的密码、超级用户属性、表空间路径等内容必须经过审查。

对于大型自定义格式转储,可以并行恢复:

pg_restore \
  --dbname=appdb_restore \
  --jobs=4 \
  --exit-on-error \
  /backup/appdb.dump

并行恢复需要目录格式或自定义格式;纯 SQL 文件不能直接交给 pg_restore --jobs。并行恢复也可能增加目标服务器的 CPU、I/O 和锁竞争,不能简单认为线程越多越快。

3.3 pg_dump 的一致性边界

pg_dump 的一致性是单个数据库、单个逻辑转储操作范围内的。它不是整个 PostgreSQL 集群的原子快照。

例如,集群中有 appdbauditdb

10:00:00  pg_dump appdb 开始
10:00:10  auditdb 中写入一条审计记录
10:00:20  pg_dump auditdb 开始

两个数据库的转储可能对应不同时间点。即使分别执行 pg_dump,也不能得到跨数据库事务的一致快照。

pg_dump 还不能恢复到“误删发生前的 10:05:00”。它只保存导出时看到的逻辑状态:

导出时状态 -> 恢复时得到该状态

它不保存:

导出之后的变化 -> 任意时间点重放

因此:

  • 迁移单库:pg_dump 很合适;
  • 恢复单表或部分对象:pg_dump 很合适;
  • 整个集群磁盘损坏后恢复:需要 Base Backup 和 WAL;
  • 恢复到误操作前某一秒:需要 PITR。

3.4 pg_dump 的其他边界

pg_dump 默认导出一个数据库,不包括整个集群的角色和表空间等全局对象。应根据需要配合:

pg_dumpall --globals-only

它还不是物理兼容性工具:

  • 逻辑转储通常可用于同一大版本内的恢复;
  • 也常用于跨大版本迁移;
  • 目标版本必须能够理解源转储中使用的对象和语法;
  • 源版本较新的特性可能无法恢复到较旧版本。

实际迁移前应先在目标版本上做完整恢复测试,而不是只检查转储命令是否成功。


4. Base Backup:物理基础备份

4.1 Base Backup 的定义

Base Backup 是 PostgreSQL 集群数据目录的物理副本。它通常通过 pg_basebackup 创建,也可以由其他物理备份工具实现。

物理备份包含:

  • 数据库表和索引的关系文件;
  • 系统目录;
  • pg_control
  • 表空间链接或表空间内容;
  • 其他数据目录文件。

它面向同一 PostgreSQL 大版本的物理恢复。不能把 PostgreSQL 15 的数据目录直接当作 PostgreSQL 16 的数据目录启动;跨大版本通常需要 pg_upgrade 或逻辑迁移。

Base Backup 覆盖的是整个集群,不是某个数据库。即使只需要恢复一个表,也不能直接把某个关系文件从备份中复制回运行中的数据库;表的数据文件还依赖系统目录、FSM、VM、索引、事务状态等整体语义。

4.2 使用 pg_basebackup

一个基础示例:

pg_basebackup \
  --host=primary.example.com \
  --port=5432 \
  --username=backup_user \
  --pgdata=/backup/base-2025-03-08 \
  --format=plain \
  --wal-method=stream \
  --progress

参数含义:

  • --pgdata:备份输出目录;
  • --format=plain:写出可直接作为数据目录使用的目录树;
  • --wal-method=stream:通过复制协议同时传输备份期间需要的 WAL;
  • --progress:显示进度。

执行用户需要具备复制权限,例如:

CREATE ROLE backup_user
    WITH LOGIN REPLICATION PASSWORD 'change-this-password';

还必须在 pg_hba.conf 中允许复制连接,例如:

host replication backup_user 10.0.0.0/24 scram-sha-256

修改认证配置后重新加载:

SELECT pg_reload_conf();

生产环境不应把密码直接写入命令行。可以使用 .pgpass,并将权限设置为只有用户本人可读:

chmod 0600 ~/.pgpass

4.3 --wal-method=streamfetch

--wal-method=stream 为 Base Backup 建立一个额外的复制连接,在备份过程中持续接收 WAL。这是通常更稳妥的选择,但会消耗一个 max_wal_senders 配置允许的发送连接。

--wal-method=fetch 则在备份结束时获取所需 WAL。它要求主库在整个备份期间保留这些 WAL;如果 WAL 已经被回收,备份会失败或不完整。因此使用 fetch 时必须保证:

  • WAL 归档持续成功,或者;
  • 通过其他方式确保所需 WAL 不被回收。

复制槽可以帮助保留 WAL,但复制槽只解决“主库暂不回收某些 WAL”的问题,不等于把 WAL 复制到了安全介质。复制槽对应的消费者永久故障时,主库的 pg_wal 可能不断增长并耗尽磁盘。

4.4 Base Backup 的一致性保证

pg_basebackup 不是简单的目录复制。服务器端会把备份过程纳入 PostgreSQL 的备份协议,记录备份边界,并保证恢复所需的 WAL 得以获取。

备份结束后,输出目录通常包含备份清单。可以用:

pg_verifybackup /backup/base-2025-03-08

验证 Base Backup 的文件清单和校验信息。它能发现文件缺失或内容变化,但不能证明:

  • 备份介质永远不会损坏;
  • WAL 归档链完整;
  • 目标时间点一定存在;
  • 恢复后的应用行为符合预期。

因此验证 Base Backup 后仍应执行实际启动恢复测试。

如果使用 PostgreSQL 版本支持的校验和,可以在集群级启用数据校验和;但校验和主要帮助发现页面损坏,不是备份机制,也不能替代异地副本。


5. WAL 归档

5.1 WAL 归档做什么

WAL 文件位于数据目录下的 pg_wal。服务器会循环使用已经不再需要的 WAL 段。若希望在基础备份之后恢复到更晚的状态,就必须在 WAL 被回收前把它们保存到独立位置。

WAL 归档的过程是:

事务产生 WAL
    ↓
WAL 写入 pg_wal
    ↓
一个 WAL 段完成
    ↓
执行 archive_command 或 archive_library
    ↓
归档目标保存该 WAL 段

WAL 归档保存的是完整 WAL 段文件,而不是每个事务一条独立文件。

常见配置方式:

archive_mode = on
archive_command = 'test ! -f /srv/pgarchive/%f && cp %p /srv/pgarchive/%f'

其中:

  • %p:源 WAL 文件的完整路径;
  • %f:WAL 文件名;
  • archive_command 返回 0 表示归档成功;
  • 返回非 0 表示失败,PostgreSQL 会保留该 WAL,并在之后重试。

archive_mode 通常需要重启才能启用;修改 archive_command 本身通常可以通过配置重载生效。新版本还支持 archive_library 等归档方式,具体取决于所使用的 PostgreSQL 大版本和归档扩展。

5.2 archive_command 的正确与错误

这个命令有一个非常重要的契约:

只有在目标文件已经安全保存后,归档命令才可以返回成功。

错误示例:

archive_command = 'cp %p /some/path/; true'

如果 cp 失败而 true 仍返回 0,PostgreSQL 会认为归档成功,之后可能回收本地 WAL。这样会形成无法恢复的 WAL 缺口。

常见的本地归档写法:

archive_command = 'test -f /srv/pgarchive/%f || cp %p /srv/pgarchive/%f'

test -f 用于让重试具有幂等性:如果目标文件已经存在,就不重复覆盖。实际生产环境还应考虑:

  • 目标目录位于独立磁盘或远程存储;
  • 复制完成后的持久化语义;
  • 网络存储暂时不可用;
  • 同名文件内容不一致时必须报警,而不能静默跳过;
  • 归档目录不能被备份工具随意清理。

仅有一个 cp 到同一台主机上的另一个目录,不能充分抵御主机、磁盘或存储控制器故障。

5.3 检查归档状态

可以查看归档统计:

SELECT
    archived_count,
    failed_count,
    last_archived_wal,
    last_archived_time,
    last_failed_wal,
    last_failed_time
FROM pg_stat_archiver;

测试归档时,可以主动切换 WAL:

SELECT pg_switch_wal();

这只会请求切换到新的 WAL 段;归档动作是异步的。随后应检查:

SELECT
    last_archived_wal,
    last_archived_time,
    failed_count,
    last_failed_wal
FROM pg_stat_archiver;

不能仅因为 pg_switch_wal() 返回了 LSN,就认为归档已经成功。

如果归档连续失败,常见现象包括:

  • pg_stat_archiver.failed_count 增加;
  • pg_wal 中 WAL 持续增长;
  • 磁盘最终耗尽;
  • 数据库写入因无法继续分配 WAL 而受到影响。

archive_timeout 可以在一段时间内没有写满 WAL 段时强制切换,降低低写入量系统的恢复延迟,但会产生更多未填满的 WAL 段,增加归档量。它不能修复归档失败。


6. 用 Base Backup 和 WAL 做 PITR

PITR 是 Point-in-Time Recovery,即时间点恢复。它不是单独的备份格式,而是 PostgreSQL 在启动恢复阶段:

  1. 载入一个 Base Backup;
  2. 执行 restore_command 获取 WAL;
  3. 按顺序重放 WAL;
  4. 在目标时间、LSN、事务 ID 或恢复名称处停止;
  5. 暂停、提升为新主库,或继续恢复。

6.1 先定义恢复目标

常见恢复目标有:

  • recovery_target_time:某个时间点;
  • recovery_target_lsn:某个 WAL LSN;
  • recovery_target_xid:某个事务 ID;
  • recovery_target_name:预先创建的恢复目标名称;
  • recovery_target='immediate':恢复到达到一致状态后尽快停止。

例如,应用在:

2025-03-08 10:15:42+08

误执行了删除操作,希望恢复到该操作之前,就应选择一个明确早于误操作提交时间的目标,而不能直接把目标时间写成“发现问题的时间”。

时间恢复的精度受 WAL 记录和事务提交记录影响。recovery_target_time 表示恢复过程要到达的时间边界;事务提交是否包含在目标状态中还受到 recovery_target_inclusive 等选项影响。生产操作不能只依赖人脑记忆的时间,最好结合应用日志、数据库审计记录和 WAL 位置确认目标。

6.2 恢复目录准备

假设:

Base Backup: /backup/base-2025-03-08
WAL 归档目录: /srv/pgarchive
目标数据目录: /var/lib/postgresql/17/main

必须先停止目标实例,然后准备一个空的数据目录:

pg_ctl -D /var/lib/postgresql/17/main stop -m fast
rm -rf /var/lib/postgresql/17/main/*
cp -a /backup/base-2025-03-08/. /var/lib/postgresql/17/main/
chown -R postgres:postgres /var/lib/postgresql/17/main

不要在运行中的 PostgreSQL 数据目录上直接覆盖文件。也不要把 Base Backup 放在源实例仍可能修改的目录中进行恢复。

在 PostgreSQL 12 及之后的版本中,恢复配置不再使用旧式独立的 recovery.conf 文件。需要在数据目录中创建 recovery.signal

touch /var/lib/postgresql/17/main/recovery.signal
chown postgres:postgres /var/lib/postgresql/17/main/recovery.signal

然后在 postgresql.conf 中配置:

restore_command = 'cp /srv/pgarchive/%f %p'

recovery_target_time = '2025-03-08 10:15:40+08'
recovery_target_action = 'pause'
recovery_target_timeline = 'latest'

这里:

  • %f 是 PostgreSQL 请求的 WAL 文件名;
  • %p 是目标数据目录中应写入该文件的路径;
  • recovery_target_action=pause 让服务器在达到目标后暂停,便于检查;
  • recovery_target_timeline=latest 允许恢复过程中跟随可用的最新时间线。

restore_command 找不到目标文件时必须返回非 0,例如:

test -f /srv/pgarchive/%f && cp /srv/pgarchive/%f %p

不要把“文件不存在”当作成功,否则 PostgreSQL 会误以为 WAL 已经被恢复。

启动实例:

pg_ctl \
  -D /var/lib/postgresql/17/main \
  -l /var/log/postgresql/pitr.log \
  start

恢复期间可以观察日志:

tail -f /var/log/postgresql/pitr.log

典型流程是:

读取 Base Backup
    ↓
读取 backup_label 中的备份信息
    ↓
调用 restore_command 获取 WAL
    ↓
重放 WAL
    ↓
达到 recovery_target_time
    ↓
进入 paused 状态

达到目标后,可以连接检查:

SELECT pg_is_in_recovery();

结果为:

 t

说明实例仍处于恢复状态。此时可以执行只读校验,例如:

SELECT count(*) FROM orders;
SELECT max(created_at) FROM orders;

确认状态后提升为独立主库:

SELECT pg_promote();

或者:

pg_ctl -D /var/lib/postgresql/17/main promote

再次检查:

SELECT pg_is_in_recovery();

结果应为:

 f

提升后,数据库进入一条新的时间线。之后在该实例上继续写入,就不应再把它当作原主库的普通延续;需要按故障切换流程重新配置应用、复制和备份。

6.3 时间线为什么重要

当数据库从某个时间点恢复并开始写入时,WAL 历史会分叉:

主时间线 1: A ─ B ─ C ─ D ─ E
                    \
新时间线 2:          C' ─ D'

时间线历史文件记录了这种分叉关系。PITR 可能需要:

  • 原始时间线上的 WAL;
  • 时间线历史文件;
  • 目标时间线上的 WAL。

因此归档系统必须保留时间线历史文件,不能只按 WAL 段文件做简单覆盖。归档仓库还必须避免不同时间线的同名 WAL 文件互相覆盖。

恢复到错误时间点后又提升为新主库,原主库可能仍然存在。此时如果不隔离旧主库,它可能继续接受写入,形成双主写入和数据分叉。这是故障切换问题,不是 pg_promote() 能自动解决的问题。


7. 一次完整的备份恢复设计

假设目标是:

  • 可以恢复整个集群;
  • 可以恢复到误操作前的时间;
  • 也可以单独恢复某个数据库或表;
  • 备份文件存放在独立存储上。

通常至少需要以下数据流:

PostgreSQL 主库
 ├── pg_dump ───────────────> 逻辑备份仓库
 ├── pg_basebackup ─────────> Base Backup 仓库
 └── WAL 归档 ──────────────> WAL 仓库

恢复路径分别是:

逻辑对象恢复:
pg_dump 文件 ──> 新数据库 ──> 选择性恢复

整库时间点恢复:
Base Backup + WAL ──> PITR ──> 新时间线上的集群

例如,某个逻辑备份在每天凌晨生成,Base Backup 每周生成,WAL 持续归档。若周三发生故障:

  1. 选取周日的 Base Backup;
  2. 从该备份要求的 WAL 起点开始,连续取得 WAL;
  3. 重放到周三故障前的目标时间;
  4. 在恢复实例上验证;
  5. 提升实例并切换应用。

如果周日 Base Backup 之后的某个 WAL 段缺失,即使周一到周三的其他 WAL 都存在,也不能无缝恢复越过这个缺口:

base ─ A ─ B ─ 缺失 ─ D ─ E

最多只能恢复到 B 所代表的可用位置。WAL 归档的“连续性”比“文件数量很多”更重要。


8. 常见误解与失败表现

8.1 “有了 pg_dump 就能 PITR”

不能。pg_dump 没有保存物理 WAL 链,无法将数据库状态重放到导出之后的任意时间。

8.2 “Base Backup 成功,所以一定能 PITR”

也不能。还需要:

  • Base Backup 本身完整;
  • 从备份所需起点到目标时间的 WAL 连续;
  • 时间线历史文件可用;
  • restore_command 能返回正确文件;
  • 目标时间确实位于可恢复历史中。

8.3 “复制副本就是备份”

流复制副本通常会重放主库的 WAL。主库上发生误删、错误更新或恶意操作后,副本也会重放同样的变化。

副本可以提高可用性和读取能力,但不自动提供:

  • 历史版本;
  • 误操作前状态;
  • 长期保留;
  • 独立故障域。

同步复制也不能替代备份,因为它会同步错误操作。

8.4 “复制槽可以代替 WAL 归档”

不能。复制槽主要用于阻止主库过早回收消费者尚未读取的 WAL。它不会保证 WAL 已写入远程备份仓库,也不会在主库磁盘损坏后提供独立副本。

8.5 restore_command 反复报错

需要先检查恢复日志中 PostgreSQL 请求的具体文件名,然后检查:

ls -l /srv/pgarchive/<requested-wal-file>

重点排查:

  • 归档文件不存在;
  • 文件权限不足;
  • 归档文件损坏;
  • %f%p 使用错误;
  • 归档仓库没有时间线历史文件;
  • 目标实例运行用户无法执行复制命令。

恢复命令找不到文件时返回非 0 是正常的:这可能意味着当前 WAL 尚未出现在归档仓库中,PostgreSQL 会按恢复流程重试或继续判断。真正危险的是命令在失败时返回 0。

8.6 恢复后查询结果不符合预期

首先确认:

SELECT pg_is_in_recovery();

如果仍在恢复中,可能因为:

  • recovery_target_action=pause
  • 目标时间尚未达到;
  • WAL 归档不完整;
  • 恢复在等待新的 WAL;
  • 选择了错误的时间线。

还应检查服务器日志中的:

  • recovery start;
  • requested WAL segment;
  • recovery target reached;
  • paused;
  • promoted;
  • timeline switch。

不要只根据 PostgreSQL 是否能启动判断恢复成功。启动成功只说明服务器找到了可启动的一致状态,不代表它已经恢复到业务要求的时刻。


9. 备份策略中的核心验证

备份系统至少要验证三个不同问题:

9.1 备份是否成功生成

逻辑备份应检查命令退出码、文件大小和归档内容:

pg_restore --list /backup/appdb.dump | head

如果文件为零字节或 pg_restore --list 无法读取,不能视为有效备份。

Base Backup 可以执行:

pg_verifybackup /backup/base-2025-03-08

9.2 WAL 是否连续可取

不能只看 archive_command 当前没有报错。应检查:

  • 归档统计;
  • 归档仓库中的 WAL 范围;
  • Base Backup 对应的起始 WAL;
  • 是否存在时间线历史文件;
  • 归档清理任务是否删除了仍需使用的 WAL。

9.3 是否真的能恢复业务

应在隔离环境中定期执行:

  1. 选择一个 Base Backup;
  2. 恢复其文件;
  3. 配置 restore_command
  4. 恢复到指定时间;
  5. 查询关键表和关键业务数据;
  6. 执行必要的应用级校验;
  7. 记录从开始恢复到可用所需的时间。

恢复测试还能测出两个重要指标:

  • RPO:最多丢失多少时间的数据;
  • RTO:从故障开始到业务恢复需要多久。

例如,WAL 归档平均延迟 2 分钟,且最后一次成功归档发生在故障前 2 分钟,那么异步归档路径的实际 RPO 可能至少接近这段延迟;但最终 RPO 仍取决于归档是否连续、目标存储是否可用以及故障发生时 WAL 是否已经安全到达归档介质。


10. 如何选择机制

可以按恢复目标选择:

只需要迁移一个数据库

使用:

pg_dump + pg_restore

它可以选择对象、修改 schema,并适合跨大版本迁移。

需要恢复整个集群到某个时间点

使用:

Base Backup + 连续 WAL 归档 + PITR

这是 PostgreSQL 物理时间点恢复的完整链路。

需要单表恢复

通常做法是:

  1. 从 Base Backup 和 WAL 创建一个临时 PITR 实例;
  2. 在临时实例中使用 pg_dump 导出目标表或目标数据库;
  3. 将逻辑转储恢复到生产环境。

这说明逻辑备份和物理恢复并非互斥:Base Backup + WAL 负责把历史状态找回来,pg_dump 负责从该状态中提取需要的对象。

需要高可用

使用流复制、同步复制、复制槽和故障切换机制,但仍应配合独立的 Base Backup、WAL 归档和恢复测试。高可用解决“尽快切换到可用节点”,备份恢复解决“找回历史状态和应对逻辑错误”。二者的故障模型不同。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。