PostgreSQL 备份恢复策略:pg_basebackup、WAL 归档与 PITR 在企业 Java 系统中的落地
1. 先看一个真实场景:备份文件在,但业务回不来
凌晨两点,一个订单系统的 DBA 被电话叫醒:有人执行了一条没有 WHERE 条件的 UPDATE,把orders表的status全部改成了CANCELED。运维的第一反应通常是“我们每天凌晨 2 点都有全量备份,恢复它就行”。可当真的开始恢复时,问题一个接一个冒出来:
- 全量备份是 20 小时前的,恢复它意味着丢掉这 20 小时内所有新订单,业务无法接受。
- 备份脚本只验证了“文件生成成功”,没人验证过“这些文件能恢复成可用实例”。
- 数据库开启了归档,但归档目录一周前就因为磁盘写满而停止接收新的 WAL,没人发现。
- 恢复出来的实例没有做时间点控制,只能回到备份那一刻,不能回到误操作前 1 秒。
这个场景说明了一件事:备份的价值不在于“备份动作成功”,而在于“在限定时间内、把数据恢复到限定的状态”。企业里的备份恢复,本质是在回答三个问题:能恢复到哪个时间点?恢复要花多久?恢复出来的数据对不对?
本文围绕 PostgreSQL 的三件核心工具展开:pg_basebackup负责拿到一份可信的全量基线,WAL 归档负责记录基线之后的所有变更,PITR(Point-In-Time Recovery,时间点恢复)负责把基线加变更重放到你指定的那个瞬间。三者缺一不可,只堆其中任何一个都无法形成闭环。
2. 一句话模型与全局框架
先用一句普通中文给出核心模型:先做一份数据库的“全量照片”,再持续记录照片之后的每一次改动,恢复时把照片按顺序叠加改动,直到停在你要的那一刻。全量照片是pg_basebackup的产物,改动记录是归档的 WAL,叠加过程就是 PITR。
把整体拆成四个部分来看,它们的关系可以用一条链路串起来:
[业务写入] --> [PostgreSQL 主库] | | 1. 全量物理拷贝 (pg_basebackup) v [基础备份目录 base.tar.gz] | | 2. 持续归档 (archive_command) v [WAL 归档目录 / 对象存储] | | 3. 恢复时重放 (recovery_target_time) v [恢复到指定时间点的新实例] --> [校验] --> [切流]这四个部分分别对应四类角色:主库产生数据与 WAL;备份工具负责在某个时刻抓取数据文件的一致性快照;归档通道把 WAL 从主库搬运到独立存储;恢复流程把基线和解归档 WAL 组装成一个可用实例。
一次正常的恢复会按这个顺序走:先在生产之外准备一台机器,把基础备份解压放好;写好restore_command指向归档目录;在postgresql.conf或postgresql.auto.conf里指定恢复目标;启动实例,PostgreSQL 会先重放基础备份里自带的 WAL,再从归档里拉取后续 WAL,一路重放到目标点,最后把实例提升为可写。
这里最容易误解的是:基础备份并不是“整个数据库目录的冷拷贝”。如果直接cp -r数据目录,你很可能拷到一个不一致的状态,比如数据页已经改了但对应的 WAL 还没落盘。pg_basebackup的价值就在于它通过 PostgreSQL 的复制协议,在数据库运行时抓取一个一致性的快照。
3. pg_basebackup:拿到一份可信的全量基线
3.1 它解决什么问题
pg_basebackup是 PostgreSQL 自带的物理备份工具,它通过流复制协议从主库读取整个数据目录和必要的 WAL,生成一份可以直接用于恢复的基础备份。它不做逻辑导出,不解析 SQL,只搬运物理文件和事务日志。
它适合的场景是:你需要一份和主库二进制一致、可以配合 WAL 归档做 PITR 的基线。它不适合的场景是:你只想导出某几张表、需要跨大版本迁移、或者需要恢复到架构不同的库,那些属于pg_dump/pg_dumpall的领域。
备份方式上,pg_basebackup支持两种模式:
- plain 模式:把文件直接写到目标目录,得到的是一个和主库布局相同的可启动目录。
- tar 模式:把数据打包成
base.tar和若干pg_wal.tar,适合传输到对象存储或做异地归档。
3.2 一个完整的最小示例
目标:在本地把一台 PostgreSQL 16 主库完整备份到/backup/base,并且带上后续 PITR 需要的 WAL。前置环境:主库运行在 5432 端口,有一个具备REPLICATION权限的用户repl,密码放在.pgpass或环境变量里。
# 前置检查:确认主库允许复制连接,并且归档已开启psql-h127.0.0.1-p5432-Upostgres-c"SHOW wal_level;"psql-h127.0.0.1-p5432-Upostgres-c"SHOW archive_mode;"psql-h127.0.0.1-p5432-Upostgres-c"SHOW archive_command;"# 执行物理基础备份:plain 模式,写入 /backup/base,附带 WALmkdir-p/backup/base pg_basebackup\-h127.0.0.1-p5432-Urepl\-D/backup/base\-Fp\-Xs\-P\-R\-cfast# 查看结果ls-lh/backup/basecat/backup/base/backup_labelcat/backup/base/postgresql.auto.conf参数说明(它们各自在链路中的位置):-Fp表示 plain 格式,直接得到可启动目录;-Xs表示把备份期间产生的 WAL 一并打包进去,这样恢复时不必依赖外部归档就能先追平到备份结束点;-P显示进度;-R会在目标目录生成standby.signal和primary_conninfo,适合直接当流复制备库用;-c fast触发一次快速检查点,缩短备份时间。
预期结果:/backup/base下出现完整的PG_VERSION、base/、global/、pg_wal/等目录;backup_label记录了备份的起始 WAL 位点,这是后续 PITR 的起点。
容易改错的地方:
- 忘记
-Xs,导致备份期间的 WAL 不在备份里,恢复时必须依赖外部归档; - 用 root 执行导致目标目录属主是 root,PostgreSQL 启动时报权限错误;
- 主库
wal_level是minimal,这样根本无法做归档和 PITR。
3.3 生产上的取舍
plain 模式简单直接,但备份过程中目标目录不能是主库正在使用的目录;tar 模式更适合做异地传输和压缩归档。如果你的数据量在几百 GB 以上,还可以考虑pg_basebackup配合--compress或外部的pgBackRest/Barman,它们在大规模增量备份、保留策略、对象存储集成上更成熟。
pg_basebackup是单线程流式读取,备份时长近似和数据库大小成正比。对 TB 级库,每次全量都可能压垮网络和 IO,所以生产上更常见的是“周期性全量 + 持续 WAL 归档”,而不是每次恢复都从头全量。
4. WAL 归档:把每次改动安全地搬出主库
4.1 WAL 是什么、为什么要归档
WAL(Write-Ahead Log,预写式日志)是 PostgreSQL 保证崩溃一致性和复制的基础。任何数据页修改前,对应的日志必须先落盘。WAL 本身是循环使用的:写完一个段(默认 16MB)后,如果不再需要,就会被覆盖。
要让 PITR 成为可能,就必须在 WAL 段被覆盖之前,把它们复制到主库之外的存储上,这就是归档。归档解决了两个问题:一是让备份之后的变更不再丢失,二是让恢复能沿着时间轴往前走。没有归档的备份,只能恢复到备份结束那一刻。
归档的核心配置在postgresql.conf中,下面给出一个可复现的最小配置:
# postgresql.conf 片段:开启 WAL 归档 wal_level = replica # 至少要 replica,PITR 需要它 archive_mode = on # 打开归档开关 archive_command = 'test ! -f /archive/%f && cp %p /archive/%f' archive_timeout = 60 # 最多 60 秒强制切换一次,缩短恢复窗口archive_command的语义非常重要:它必须满足“文件已经存在就不要覆盖,成功返回 0,失败返回非 0”。%p是主库上的 WAL 路径,%f是文件名。PostgreSQL 会不断重试同一条命令,直到它返回 0,然后才允许覆盖本地 WAL。这意味着如果归档目录不可写,主库的pg_wal会逐渐涨满,最终导致写入失败。
4.2 一个可观测的归档脚本示例
生产环境不建议用裸cp,因为它没有错误上报、没有校验、没有分级存储。下面是一个更贴近生产的归档脚本示例,目标是归档到一个按日期分目录的存储,并在失败时留下可排查的日志。
#!/usr/bin/env bash# /usr/local/bin/pg_archive.sh# 输入:%p 主库 WAL 路径,%f WAL 文件名set-euopipefailWAL_PATH="$1"WAL_NAME="$2"ARCHIVE_ROOT="/archive/pg"DAY_DIR="${ARCHIVE_ROOT}/$(date+%Y%m%d)"TARGET="${DAY_DIR}/${WAL_NAME}"mkdir-p"${DAY_DIR}"# 幂等:已存在则直接成功退出if[-f"${TARGET}"];thenexit0ficp"${WAL_PATH}""${TARGET}.tmp"# 用 sha256 保证搬运过程没有损坏sha256sum"${TARGET}.tmp">"${TARGET}.tmp.sha256"mv"${TARGET}.tmp""${TARGET}"echo"$(date-Iseconds)archived${WAL_NAME}">>"${ARCHIVE_ROOT}/archive.log"exit0在postgresql.conf中把archive_command指向它:
archive_command = '/usr/local/bin/pg_archive.sh %p %f'关键步骤和结果:每次 WAL 切换时,脚本把段文件复制到以日期命名的目录里,同时生成校验文件。test ! -f或脚本里的存在性判断保证了幂等,重复调用不会破坏已有文件。失败时脚本返回非 0,PostgreSQL 会保留本地 WAL 并持续重试。
边界和常见错误:归档目录和主库必须在不同物理盘或不同机器上,否则一盘损坏会同时带走数据文件和归档;脚本必须有超时控制,否则一次网络卡顿会让 WAL 堆积;archive_mode从off改成on需要重启,从on改成always只需 reload,这些版本相关行为要以官方文档为准。
4.3 归档监控:不要只看“有没有报错”
归档失败往往是静默的。archive_command失败不一定立刻让业务报错,它只是让 WAL 堆积。可以在主库上周期性检查:
-- 查看归档统计:成功数、失败数、最后一次归档的 WALSELECTarchived_count,failed_count,last_archived_wal,last_archived_time,last_failed_wal,last_failed_timeFROMpg_stat_archiver;如果failed_count持续增长,或者last_archived_time距离现在超过archive_timeout的若干倍,就要告警。还有一个更直接的信号:pg_wal目录里累积的段数量持续上升,说明旧 WAL 一直没被归档成功释放。
5. PITR:把基线和高频变更重放到指定瞬间
5.1 一句话理解 PITR 的状态流转
可以把 PITR 理解成“在一台新机器上重演历史”:先把基础备份铺开,然后让 PostgreSQL 从备份结束点开始,按顺序读取归档 WAL,一条条重放,直到重放到你指定的时间点、事务 ID 或恢复点名称,最后停下并允许写入。
整体状态流转如下:
[基础备份目录] --启动--> [进入恢复模式 crash recovery] | | restore_command 拉取归档 WAL v [持续重放 WAL] --到达 recovery_target_time--> [一致性停止点] | | pg_ctl promote 或 recovery_target_action 触发 v [实例变为可写] --> [校验业务数据]5.2 一个完整的 PITR 恢复示例
目标:把备份/backup/base和归档/archive/pg恢复成一台新实例,恢复到 2024-06-01 10:30:00 之前的状态,模拟“误删数据前 1 分钟”。前置环境:已安装同版本 PostgreSQL 16,新实例数据目录/var/lib/pgsql/16/data,端口 5433,避免和生产冲突。
# 1. 停止目标实例,清空数据目录pg_ctl-D/var/lib/pgsql/16/data stop-mfast||truerm-rf/var/lib/pgsql/16/data/*# 2. 拷贝基础备份(plain 模式下直接复制文件)cp-a/backup/base/. /var/lib/pgsql/16/data/chown-Rpostgres:postgres /var/lib/pgsql/16/datachmod700/var/lib/pgsql/16/data# 3. 指定恢复命令和恢复目标cat>>/var/lib/pgsql/16/data/postgresql.auto.conf<<'EOF' restore_command = 'cp /archive/pg/20240601/%f %p' recovery_target_time = '2024-06-01 10:30:00+08' recovery_target_action = 'promote' EOF# 4. 触发恢复sudo-upostgres pg_ctl-D/var/lib/pgsql/16/data start# 5. 观察日志:确认重放到目标点并提升tail-f/var/lib/pgsql/16/data/log/postgresql-*.log预期结果:日志里会看到restored log file "0000000100000000000000A1" from archive、recovery stopping before commit of transaction ...、database system is ready to accept connections。启动后实例可写,SELECT pg_is_in_recovery();返回f。
关键步骤说明:restore_command的作用和archive_command对称,负责把归档 WAL 拉回本地;recovery_target_time决定停在哪里;recovery_target_action = 'promote'表示到达目标后自动提升。如果只想恢复到一致性点并保持只读做校验,可以用pause,手动检查后再 promote。
容易改错的地方:
- 归档目录里没有备份结束点之后的 WAL,恢复会停在一个较早的位置;
recovery_target_time时区写错,导致恢复点偏差几小时;- 用
cp /archive/pg/%f这种不带日期目录的路径,因为归档按日期分区而找不到文件。
5.3 恢复目标怎么选
PITR 支持多种恢复目标,选择哪种取决于你要“精确回到某个瞬间”还是“回到某个逻辑检查点”:
| 恢复目标参数 | 含义 | 典型场景 | 注意点 |
|---|---|---|---|
recovery_target_time | 恢复到指定时间戳 | 误操作前 1 分钟 | 依赖时间准确性,事务提交时间可能存在边界模糊 |
recovery_target_xid | 恢复到指定事务 ID | 已知误操作事务号 | 需要先从事务日志或审计里定位 xid |
recovery_target_lsn | 恢复到指定 WAL 位点 | 精确到字节级 | LSN 不易人工获取,适合工具驱动 |
recovery_target_name | 恢复到已创建的恢复点 | 变更窗口前后打点 | 需要在正常运行时提前pg_create_restore_point |
recovery_target | 恢复到最近的一致性点 | 只要求可用,不要求精确 | 得到的是备份结束点或最近可恢复点 |
对企业 Java 系统来说,最常见的组合是:在发布前用pg_create_restore_point打一个命名恢复点,配合recovery_target_name做发布回滚;遇到人为误操作,用recovery_target_time精确回退。
6. 恢复窗口、RPO 与 RTO 怎么算
6.1 三个时间概念
备份恢复不能只看“有没有备份”,要看三个时间量:
- RPO(Recovery Point Objective,恢复点目标):最多能容忍丢多少数据,通常表现为时间。
- RTO(Recovery Time Objective,恢复时间目标):最多能容忍业务中断多久。
- 恢复窗口:从决定恢复到实例可用,这段过程实际花的时间,由“找备份 + 解压 + 重放 WAL + 校验 + 切流”组成。
它们的关系可以用下面的式子近似表达:
可恢复的最早时间点 = 基础备份开始时刻 可恢复的最晚时间点 = 最后一个成功归档的 WAL 时刻 实际 RPO = 当前时间 - 可恢复的最晚时间点 RTO ≈ 备份加载时间 + WAL 重放时间 + 校验与切流时间这意味着:即使你做了一次全量,如果没有持续归档,可恢复的最晚时间点会一直停在备份结束点,RPO 会随时间无限变大。archive_timeout越小,RPO 上限越小,但归档频率越高、存储和网络压力越大。
6.2 数字例子
假设数据库 200GB,pg_basebackup速率 100MB/s,那么仅拷贝基础备份就要约 34 分钟。若每小时产生 2GB WAL,恢复到 3 小时前需要重放约 6GB WAL;在恢复机器上重放速率约 50MB/s,则需要约 2 分钟。再叠加 5 分钟校验与切流,RTO 大约 40 分钟。如果你的业务要求 RTO 小于 15 分钟,这个方案就不达标,需要考虑流复制备库做故障切换,PITR 只作为“回到过去某点”的手段。
这个例子的意义是:RTO 不是备份工具单点决定的,而是整条链路的最慢环节决定的。常见的瓶颈是基础备份太大、WAL 重放太慢、恢复机器磁盘 IO 弱、校验和切流没有自动化。
7. 恢复之后:备份校验与演练
7.1 三类校验
“备份成功”和“备份可用”是两件事。工程上至少要做三类校验:
- 文件级校验:备份文件是否完整、能否解压、校验和是否匹配。
pg_basebackup的backup_label和pg_verifybackup可以帮你检查基础备份的完整性。 - 可启动校验:把备份恢复成一台临时实例,确认能启动、能连接、
SELECT正常。 - 业务级校验:对关键表做行数、聚合值或抽样比对,确认恢复出来的数据业务上可用。
下面是一个可以周期性运行的校验脚本示例,目标是验证基础备份的完整性:
#!/usr/bin/env bash# /usr/local/bin/verify_backup.sh# 输入:备份目录,如 /backup/baseset-euopipefailBACKUP_DIR="$1"# 1. 目录是否存在且包含 backup_labeltest-f"${BACKUP_DIR}/backup_label"echo"backup_label found:"cat"${BACKUP_DIR}/backup_label"# 2. 使用 pg_verifybackup 校验备份完整性pg_verifybackup"${BACKUP_DIR}"# 3. 检查是否存在 PostgreSQL 版本文件test-f"${BACKUP_DIR}/PG_VERSION"echo"PG_VERSION=$(cat${BACKUP_DIR}/PG_VERSION)"echo"verify ok:${BACKUP_DIR}"执行结果:pg_verifybackup会输出每个文件的校验结果,如有缺失会返回非 0。这个脚本可以放进监控,每周跑一次并告警。
7.2 业务级校验的 SQL 示例
目标:在恢复出的临时实例上,确认订单表的关键指标和生产某个历史时点一致。前置环境:已启动恢复实例,端口 5433。
-- 在恢复实例上执行:检查订单总数与状态分布SELECTstatus,count(*)AScntFROMordersGROUPBYstatusORDERBYstatus;-- 检查最近 1 小时的订单是否存在(用于判断恢复点是否到位)SELECTcount(*)ASrecent_ordersFROMordersWHEREcreated_at>=now()-interval'1 hour';-- 检查关键表的行数SELECTrelname,n_live_tupFROMpg_stat_user_tablesWHERErelnameIN('orders','order_items','payments');结果解读:如果recent_orders为 0,说明恢复点过于靠前;如果状态分布异常(例如全部是CANCELED),说明恢复点停在了误操作之后。这些检查应写进恢复演练的 checklist,而不是靠人工临时判断。
8. 和 Spring Boot 应用怎么衔接
8.1 应用侧的配置要点
对 Spring Boot 应用来说,备份恢复不是应用直接调用的功能,但应用侧的配置会直接影响恢复质量。两个关键点:连接串要能指向恢复实例,写入要能快速切走。
下面是一个application.yml示例,目标是把数据库地址、连接池和故障切换解耦,便于恢复时改指向临时实例:
spring:datasource:url:jdbc:postgresql://db-primary.internal:5432/orderdb?targetServerType=primaryusername:order_apppassword:${DB_PASSWORD}hikari:maximum-pool-size:20connection-timeout:3000validation-timeout:1000keepalive-time:30000management:health:db:enabled:truetargetServerType=primary让 JDBC 驱动在多主机场景下优先选择主库;connection-timeout和validation-timeout让故障实例被快速识别而不是长时间挂起。恢复期间,运维把域名db-primary.internal指向恢复实例即可,应用无需改代码。
8.2 一个用于恢复前“冻结写入”的最小 Spring Boot 示例
目标:在决定恢复前,先让应用停止写入,避免恢复出来的数据和正在跑的实例继续分叉。前置环境:Spring Boot 3.x + Spring Data JPA,数据库为 PostgreSQL。
packagecom.example.backup;importorg.springframework.boot.actuate.health.Health;importorg.springframework.boot.actuate.health.HealthIndicator;importorg.springframework.jdbc.core.JdbcTemplate;importorg.springframework.stereotype.Component;importjava.util.concurrent.atomic.AtomicBoolean;/** * 恢复窗口期间用于控制写入的开关。 * 生产上通常由运维通过配置中心或发布系统触发。 */@ComponentpublicclassWriteGateimplementsHealthIndicator{privatefinalAtomicBooleanwritesEnabled=newAtomicBoolean(true);privatefinalJdbcTemplatejdbcTemplate;publicWriteGate(JdbcTemplatejdbcTemplate){this.jdbcTemplate=jdbcTemplate;}/** * 冻结写入前,确认当前实例可连接,并记录一个用于对账的 LSN。 * 返回的 LSN 可以作为后续恢复目标或校验参考。 */publicStringfreezeWrites(){writesEnabled.set(false);Stringlsn=jdbcTemplate.queryForObject("SELECT pg_current_wal_lsn()::text",String.class);returnlsn;}publicvoidresumeWrites(){writesEnabled.set(true);}publicbooleanisWritesEnabled(){returnwritesEnabled.get();}@OverridepublicHealthhealth(){if(writesEnabled.get()){returnHealth.up().withDetail("writes","enabled").build();}returnHealth.outOfService().withDetail("writes","frozen").build();}}关键步骤说明:freezeWrites()把写入开关置为关闭,并读取当前 WAL 位点;健康检查返回OUT_OF_SERVICE,方便负载均衡把流量摘走。恢复完成后调用resumeWrites()恢复写入。
适用场景与边界:这个示例只演示了“冻结开关”的思路,真实系统需要在业务写入入口(如 Service 层或拦截器)真正检查这个开关,否则只是改了一个无人读取的变量。pg_current_wal_lsn()返回的是当前写入位点,可以作为恢复后的对账参考,但不能直接当作 PITR 目标,因为 PITR 目标通常由归档侧的时间点决定。
9. 常见误区
9.1 把 pg_dump 当 PITR 的基础
pg_dump是逻辑备份,得到的是 SQL 或自定义格式的数据快照,不包含物理层面的 WAL。它适合迁移、单表导出、跨版本升级,但不能配合 WAL 归档做时间点恢复。想做 PITR,起点必须是物理基础备份。
9.2 以为“备份成功就是可恢复”
备份脚本返回 0 只说明文件写出去了。文件是否完整、能否启动、数据是否一致,需要单独的校验环节。没有恢复演练的备份,和没有备份差别不大。
9.3 归档目录和主库放同一块盘
这是最危险的配置之一。主库磁盘损坏时,数据文件和归档同时丢失,PITR 直接失效。归档至少要放到不同机器或对象存储。
9.4 忽略 archive_command 的失败重试特性
PostgreSQL 会反复重试同一条archive_command,并且不会覆盖本地 WAL。如果命令因为权限或路径问题一直失败,pg_wal会持续增长,最终写满磁盘导致主库不可写。归档失败必须告警。
9.5 恢复时忘记摘流
恢复实例启动后,如果应用连接串还指向它、且它有写入流量,可能产生新的分叉数据。恢复期间的正确顺序是:先摘流或冻结写入,再启动恢复实例,校验通过后再切流。
10. 生产实践建议
- 基础备份和 WAL 归档必须同时存在。只有基础备份,RPO 等于备份间隔;只有归档没有基线,无法快速重建。
- 给归档目录做容量告警和生命周期管理。WAL 归档量随写入量增长,保留策略要覆盖最长的恢复窗口需求。
- 定期做恢复演练。至少每季度一次,从备份恢复到一台临时实例,验证可启动、可查询、关键指标正确,并记录实际 RTO。
- 命名恢复点用于发布回滚。在重要发布前执行
pg_create_restore_point('release_20240601'),出问题时可以按名称精确恢复。 - 恢复目标和时区写清楚。跨时区团队尤其要统一时区,避免恢复到错误的时间点。
- 把恢复步骤脚本化。手工恢复容易漏步骤,把拷贝、配置、启动、校验写成脚本,降低人为失误。
11. 排障清单
| 现象 | 可能原因 | 排查命令或位置 |
|---|---|---|
| 恢复后数据停在很早的时点 | 归档缺失或 restore_command 路径错误 | 看pg_stat_archiver、检查归档目录文件 |
| 恢复日志报找不到 WAL 文件 | 归档未覆盖备份结束点之后的段 | 对比backup_label中的起始位点与归档列表 |
| 主库写入变慢、pg_wal 膨胀 | archive_command 持续失败 | SELECT * FROM pg_stat_archiver;看 failed_count |
| 恢复实例无法启动 | 数据目录权限、属主或配置错误 | 检查postgresql.auto.conf、数据目录权限 |
| 恢复后业务校验不通过 | 恢复目标选错或时区偏差 | 检查recovery_target_time与时区设置 |
| 恢复耗时远超预期 | 基础备份过大或恢复机器 IO 弱 | 分阶段计时:拷贝、解压、重放、校验 |
12. 面试/复盘问题
- 为什么 PITR 必须同时依赖基础备份和 WAL 归档?缺少任一会发生什么?
pg_basebackup的-Xs和-Xf有什么区别,分别在什么场景使用?archive_command为什么必须幂等,失败重试会对主库产生什么影响?recovery_target_time和recovery_target_name各自适合什么业务场景?- 如果 RTO 要求小于 15 分钟,PITR 和流复制备库各自扮演什么角色?
- 如何设计一套“备份是否真的可恢复”的周期性校验机制?
- 恢复期间应用侧需要做哪些配合动作,为什么不能只靠 DBA 操作?
13. 总结:把知识收回一张决策表
回到开头那个凌晨的场景,一个成熟的团队应该能在 40 分钟内完成:确认恢复目标时间点、在临时机器上铺开基础备份、通过归档 WAL 重放到目标点、启动实例、跑业务校验、切流。要做到这一点,靠的不是某个神奇命令,而是整条链路都经过了验证。
把本文的关键选择收进一张决策表:
| 你的目标 | 推荐手段 | 关键配置或命令 | 主要限制 |
|---|---|---|---|
| 回到某个时间点 | 基础备份 + WAL 归档 + PITR | pg_basebackup、archive_command、recovery_target_time | 依赖归档连续性和恢复演练 |
| 快速切换故障实例 | 流复制备库 + 切换 | primary_conninfo、pg_ctl promote | 无法回到过去,只能接管当前 |
| 单表或跨版本迁移 | 逻辑备份 | pg_dump/pg_restore | 不适合大规模 PITR |
| 发布前打点 | 命名恢复点 | pg_create_restore_point | 需要在发布前主动执行 |
| 验证备份可用性 | 文件校验 + 恢复演练 | pg_verifybackup、临时实例启动 | 需要额外资源和流程 |
最后记住一句话:全量基线让你有起点,WAL 归档让你能往前走,PITR 让你能停在指定的一刻,而校验和演练让你确信这三件事真的成立。
14. 参考资料
- PostgreSQL 官方文档:Backup and Restore(Backup and Restore 章节)
- PostgreSQL 官方文档:Continuous Archiving and Point-in-Time Recovery(PITR)
- PostgreSQL 官方文档:Write-Ahead Logging(WAL)
- PostgreSQL 官方文档:pg_basebackup
- PostgreSQL 官方文档:pg_verifybackup
- PostgreSQL 官方文档:pg_stat_archiver 视图
- PostgreSQL 官方文档:Recovery Configuration(restore_command、recovery_target_* 参数)