系统学习 学习路径总入口:先选领域,再建立模型,最后用故障演练验证

用基础备份和连续 WAL 归档把 PostgreSQL 恢复到指定时间点,并验证、切换或回滚

适用场景

PITR 依赖“基础备份 + 从备份开始到目标时间点的连续 WAL”。只有基础备份没有 WAL 归档,只能恢复到备份结束时刻;只有 WAL 没有可用基础备份,也无法完成恢复。物理 PITR 不能只恢复单张表;只回收单表时优先考虑逻辑导出。

本流程默认恢复到独立的新实例,不覆盖原数据库。原实例保留为取证和回切路径,恢复实例在验证通过后再接管流量。

前置检查

  1. 明确恢复目标。记录事故发生的准确时间、时区、涉及的表或事务,以及业务能接受的数据丢失范围。时间点必须使用数据库能识别的时区,例如 2026-10-18 02:30:00+08;不要用“大概半小时前”这样的描述。

  2. 确认基础备份和 WAL 归档可用:

psql -Atc "show wal_level; show archive_mode; show archive_command;"
psql -c "select archived_count, failed_count, last_archived_wal, last_archived_time from pg_stat_archiver;"
pg_verifybackup /backup/basebackup

基础备份应带有 backup_manifest,校验失败就不要使用。检查备份起始 WAL、目标时间之前的 WAL 段、时间线历史和归档目录权限;目录非空不代表 WAL 连续,缺失一个段就可能让恢复停在错误的位置。

  1. 确认目标实例的 PostgreSQL 主版本、扩展、操作系统依赖和字符集与备份来源一致。恢复主机要有足够的磁盘空间容纳数据目录、WAL 回放和临时文件;恢复端口不能与生产实例冲突。

  2. 检查恢复目标是否在基础备份之后、当前可用 WAL 之前。目标早于基础备份结束时刻时,这份基础备份不能作为起点;目标晚于最后一份 WAL 时,只能恢复到最后一个可回放点,剩余数据无法凭现有归档补回。

  3. 冻结源库写入。暂停应用、批处理、CDC 和定时任务,记录切换前的 LSN 与时间:

psql -Atc "select pg_current_wal_lsn(), clock_timestamp();"

源库无法写入时不要反复重启,改用事故前已验证的基础备份和 WAL。

  1. 保留原数据目录、WAL 归档和恢复日志。恢复期间不要删除旧实例,也不要让恢复实例覆盖生产归档中的同名文件。

  2. 确定验收标准。业务方要能回答:恢复到哪个时间点、哪些表必须存在、关键行数和校验值是多少、哪些写入允许丢失、切换后如何回切。

恢复步骤

1. 准备空的恢复目录

在独立主机或独立端口上创建目录,属主必须是运行 PostgreSQL 的系统用户:

sudo install -d -o postgres -g postgres -m 0700 /restore/pgdata
sudo install -d -o postgres -g postgres /restore/log

如果直接使用已经校验过的 /backup/basebackup,可以跳过下面的拉取步骤;从备份库重新拉取时,先按同样标准校验新备份。不要把基础备份直接解压到生产 PGDATA。使用 pgBackRest、WAL-G 或 Barman 时,按其命令恢复到新目录;验证和切换原则相同。

2. 从基础备份恢复

使用与源库兼容的 pg_basebackup 从备份库或只读副本拉取:

sudo -u postgres pg_basebackup \
  -h backup-host \
  -D /restore/pgdata \
  -U repl \
  -Fp -Xs -P

如果基础备份是归档格式,先按工具说明解包到空目录,再检查 PG_VERSION、表空间映射和文件权限。不要边解包边启动数据库。

3. 配置 WAL 回放和目标时间

在恢复实例的 postgresql.conf 末尾追加恢复参数,或使用独立的 include 文件。下面假设 WAL 已挂载到 /archive/wal:

sudo -u postgres tee -a /restore/pgdata/postgresql.conf >/dev/null <<'EOF'
restore_command = 'cp /archive/wal/%f %p'
recovery_target_time = '2026-10-18 02:30:00+08'
recovery_target_inclusive = off
recovery_target_action = 'pause'
recovery_target_timeline = 'latest'
hot_standby = on
port = 5433
listen_addresses = '127.0.0.1'
EOF

recovery_target_inclusive = off 表示停在目标时间之前,适合目标时间正是坏事务开始时刻的情况。若只是想把数据库带到某个正常时点,可按需要改成 on 或省略。recovery_target_action = 'pause' 让实例在到达目标后暂停,先做只读验证,再手动 promote;不要一上来就自动开放写入。

如果事故前创建过命名还原点,也可以使用 recovery_target_name;如果已知精确 LSN,可使用 recovery_target_lsn。一次只设置一个恢复目标,避免相互冲突。

4. 启动并观察回放

sudo -u postgres touch /restore/pgdata/recovery.signal
sudo -u postgres pg_ctl -D /restore/pgdata -l /restore/log/recovery.log start
sudo tail -f /restore/log/recovery.log

日志中应看到恢复启动、WAL 回放和目标时间信息,不应出现找不到 WAL、时间线不匹配或文件权限错误。到达目标后,实例进入 pause 状态,此时只允许只读查询。

5. 只读验证

psql -h 127.0.0.1 -p 5433 -U postgres -d app \
  -c "select pg_is_in_recovery(), pg_last_wal_replay_lsn(), pg_last_xact_replay_timestamp();"

重点核对:

如果目标时间选错,不要在这个已回放的目录上反复改参数重启;直接停止实例,换一个全新的空目录,从基础备份重新开始。

6. 提升并开放写入

只读验证通过后,在两个独立会话确认没有遗漏,再提升:

psql -h 127.0.0.1 -p 5433 -U postgres -d postgres -c "select pg_promote();"
psql -h 127.0.0.1 -p 5433 -U postgres -d app -c "select pg_is_in_recovery();"

第二条命令应返回 f。随后执行一次受控写事务,例如写入并回滚一条测试记录,确认表、索引、触发器和权限都允许写入。再按应用验收清单做登录、下单或关键流程测试。

恢复实例接管前,要处理归档和新备份:把 archive_command 指向新的归档路径或带时间线隔离的前缀,确认恢复后的 WAL 不会覆盖原归档;随后立即做一次新的基础备份。没有新的基础备份,下一次恢复仍然要依赖旧的归档链。

验证

失败与回滚

WAL 段缺失。 回放在缺口处停止或报错,不能跳过缺失段继续。把目标时间改到缺口之前,或从其他归档、副本、对象存储找回该段;找不到就只能接受更早的恢复点。不要用 pg_resetwal 绕过 WAL 缺失,这会破坏一致性并可能让数据库无法再用于生产。

目标时间早于基础备份。 当前基础备份无法满足目标,换用更早的基础备份和对应 WAL。不要尝试把当前基础备份“往前恢复”。

基础备份损坏或扩展缺失。 重新从另一份已校验的备份恢复;扩展和共享库必须安装到目标主机,缺扩展时不要先 promote 再补。

目标时间选错。 停止恢复实例,保留日志和已回放目录,换一个全新的空目录重新恢复。不要在同一个数据目录上多次启动、修改 recovery_target_time,残留状态会让问题更难判断。

提升后才发现数据不对。 一旦恢复实例接受写入,就不能简单“回退 WAL”。应立即停止新写入,保留恢复实例,判断旧实例是否仍然一致且可作为回切目标。若旧实例在冻结后没有新写入,可以把应用切回;若旧实例有独立写入,必须先做差异核对,不能制造两个可写主库。

回切到旧实例。 先隔离恢复实例,再切回应用连接串,确认旧实例的数据和复制状态。恢复实例上已经产生的新数据要导出并评估是否补回;不要直接覆盖旧实例的数据目录。

恢复实例反复重启。 检查 recovery.signal、restore_command 路径、WAL 权限、时间线历史和配置重复项。不要把生产配置整套复制过来,尤其不要带入旧的 port 和 archive_command。

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 只有基础备份、没有连续 WAL 归档,为什么无法恢复到基础备份之后的任意时间点?
  2. recovery_target_action = 'pause' 的作用是什么,为什么不应直接自动 promote?
  3. WAL 在目标时间之前出现缺口时,为什么不能跳过缺口继续恢复?
答案
  1. PITR 要在基础备份之上重放连续 WAL 才能到达目标时间;缺少 WAL 只能停在备份结束点或最后一个可回放点。
  2. 它让实例到达目标后暂停,便于只读核对数据和日志;先验证再手动 promote,可以避免把错误时间点或缺失扩展的实例直接开放写入。
  3. WAL 是有顺序的日志链,跳过缺口会丢失中间事务并破坏一致性;应改到缺口之前的恢复目标,或找回缺失的归档段。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令