DELETE、UPDATE、DDL 或错误数据迁移,需要回到事故前的某个时间点。PITR 依赖“基础备份 + 从备份开始到目标时间点的连续 WAL”。只有基础备份没有 WAL 归档,只能恢复到备份结束时刻;只有 WAL 没有可用基础备份,也无法完成恢复。物理 PITR 不能只恢复单张表;只回收单表时优先考虑逻辑导出。
本流程默认恢复到独立的新实例,不覆盖原数据库。原实例保留为取证和回切路径,恢复实例在验证通过后再接管流量。
明确恢复目标。记录事故发生的准确时间、时区、涉及的表或事务,以及业务能接受的数据丢失范围。时间点必须使用数据库能识别的时区,例如 2026-10-18 02:30:00+08;不要用“大概半小时前”这样的描述。
确认基础备份和 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 连续,缺失一个段就可能让恢复停在错误的位置。
确认目标实例的 PostgreSQL 主版本、扩展、操作系统依赖和字符集与备份来源一致。恢复主机要有足够的磁盘空间容纳数据目录、WAL 回放和临时文件;恢复端口不能与生产实例冲突。
检查恢复目标是否在基础备份之后、当前可用 WAL 之前。目标早于基础备份结束时刻时,这份基础备份不能作为起点;目标晚于最后一份 WAL 时,只能恢复到最后一个可回放点,剩余数据无法凭现有归档补回。
冻结源库写入。暂停应用、批处理、CDC 和定时任务,记录切换前的 LSN 与时间:
psql -Atc "select pg_current_wal_lsn(), clock_timestamp();"
源库无法写入时不要反复重启,改用事故前已验证的基础备份和 WAL。
保留原数据目录、WAL 归档和恢复日志。恢复期间不要删除旧实例,也不要让恢复实例覆盖生产归档中的同名文件。
确定验收标准。业务方要能回答:恢复到哪个时间点、哪些表必须存在、关键行数和校验值是多少、哪些写入允许丢失、切换后如何回切。
在独立主机或独立端口上创建目录,属主必须是运行 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 时,按其命令恢复到新目录;验证和切换原则相同。
使用与源库兼容的 pg_basebackup 从备份库或只读副本拉取:
sudo -u postgres pg_basebackup \
-h backup-host \
-D /restore/pgdata \
-U repl \
-Fp -Xs -P
如果基础备份是归档格式,先按工具说明解包到空目录,再检查 PG_VERSION、表空间映射和文件权限。不要边解包边启动数据库。
在恢复实例的 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。一次只设置一个恢复目标,避免相互冲突。
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 状态,此时只允许只读查询。
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();"
重点核对:
FATAL、PANIC、WAL 缺失或页面校验错误。如果目标时间选错,不要在这个已回放的目录上反复改参数重启;直接停止实例,换一个全新的空目录,从基础备份重新开始。
只读验证通过后,在两个独立会话确认没有遗漏,再提升:
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 不会覆盖原归档;随后立即做一次新的基础备份。没有新的基础备份,下一次恢复仍然要依赖旧的归档链。
pg_last_xact_replay_timestamp() 与预期接近,没有缺失 WAL 的报错。pg_is_in_recovery() 返回 f,新写入成功,旧连接被正确切换或重建。pg_stat_archiver 的失败计数不再增长,并完成一次新的基础备份。WAL 段缺失。 回放在缺口处停止或报错,不能跳过缺失段继续。把目标时间改到缺口之前,或从其他归档、副本、对象存储找回该段;找不到就只能接受更早的恢复点。不要用 pg_resetwal 绕过 WAL 缺失,这会破坏一致性并可能让数据库无法再用于生产。
目标时间早于基础备份。 当前基础备份无法满足目标,换用更早的基础备份和对应 WAL。不要尝试把当前基础备份“往前恢复”。
基础备份损坏或扩展缺失。 重新从另一份已校验的备份恢复;扩展和共享库必须安装到目标主机,缺扩展时不要先 promote 再补。
目标时间选错。 停止恢复实例,保留日志和已回放目录,换一个全新的空目录重新恢复。不要在同一个数据目录上多次启动、修改 recovery_target_time,残留状态会让问题更难判断。
提升后才发现数据不对。 一旦恢复实例接受写入,就不能简单“回退 WAL”。应立即停止新写入,保留恢复实例,判断旧实例是否仍然一致且可作为回切目标。若旧实例在冻结后没有新写入,可以把应用切回;若旧实例有独立写入,必须先做差异核对,不能制造两个可写主库。
回切到旧实例。 先隔离恢复实例,再切回应用连接串,确认旧实例的数据和复制状态。恢复实例上已经产生的新数据要导出并评估是否补回;不要直接覆盖旧实例的数据目录。
恢复实例反复重启。 检查 recovery.signal、restore_command 路径、WAL 权限、时间线历史和配置重复项。不要把生产配置整套复制过来,尤其不要带入旧的 port 和 archive_command。
合上页面,先回答,再展开对照。
recovery_target_action = 'pause' 的作用是什么,为什么不应直接自动 promote?
小程序版
微信扫码,随时查命令