2.6 最小逻辑备份闭环
生成一个 dump 文件只是开始。最小闭环必须回答:文件里有什么、由哪个版本生成、能否在隔离目标恢复、恢复后的状态是否符合预期、哪些生产恢复目标仍未覆盖。
2.6.1 pg_dump 的对象、模式与自定义格式
pg_dump 连接一个数据库,从一致快照读取逻辑对象定义与数据。它不会阻塞普通读写,但会持有访问共享锁来防止被导出的表在过程中遭到破坏性 DDL;长时间快照也可能影响 vacuum 回收。生产上不能因为“在线 dump”就忽略运行窗口和监控。
先验证夹具,再创建目录:
mkdir -p evidence/ch02/backup
psql -X -w "service=pg36-admin" \
-v ON_ERROR_STOP=1 \
-f verify.sql \
>evidence/ch02/backup/source-verify.txt \
2>evidence/ch02/backup/source-verify.stderr使用自定义格式:
pg_dump -w \
--format=custom \
--verbose \
--file=evidence/ch02/backup/pg36_shop.dump \
"service=pg36-admin application_name=pg36-ch02-dump" \
>evidence/ch02/backup/dump.stdout \
2>evidence/ch02/backup/dump.stderr-w 禁止密码提示;凭据来自 passfile。不要把 stdout 和 stderr 合并,因为 pg_dump --verbose 的进度与警告写入 stderr,警告必须进入验收。
为什么选择 custom
| 格式 | 恢复工具 | 选择对象 | 并行能力 | 本章用途 |
|---|---|---|---|---|
| plain | psql | 生成后可读但难安全重排 | 无 | 审查简单 SQL |
custom (-Fc) | pg_restore | 可列清单、过滤、重排 | 恢复可并行 | 本章默认 |
directory (-Fd) | pg_restore | 可列清单、过滤、重排 | dump 与 restore 可并行 | 大库与并行任务 |
tar (-Ft) | pg_restore | 可选择 | 不支持并行 dump | 兼容特定归档流程 |
custom 默认压缩且是单文件,适合小型教学闭环。真正的大库可能选择 directory format 与并行 worker,但并行数必须结合服务器 CPU、存储、网络和锁评估。
dump 的边界
普通 pg_dump 只处理一个数据库。它不包含跨数据库共享的角色与表空间定义;这类全局对象可由:
pg_dumpall -w --globals-only \
--database="service=pg36-admin dbname=postgres" \
>evidence/ch02/backup/globals.sql单独导出。globals 文件可能含有角色口令哈希与敏感 ACL,应按秘密材料保护。本章恢复演练使用 --no-owner --no-privileges,故不依赖它;这也意味着恢复结果有意不保留原 owner/ACL,不能冒充完整灾备演练。
pg_dump -t 或 --schema 只选择匹配对象,不会自动保证所有依赖都包含。一个“成功生成”的局部 dump 可能无法恢复到空数据库。除非任务明确处理依赖,本章先 dump 整个 pg36_shop。
客户端版本也是输入
先记录:
pg_dump --version
psql -X -w "service=pg36-admin" -Atc \
"SELECT current_setting('server_version')"pg_dump 不能导出主要版本高于自己的服务器;较新的 pg_dump 可以读取较老服务器,但输出通常面向较新工具链。逻辑 dump 常用于升级,却没有“向旧版本降级必然成功”的承诺。扩展、排序规则和 SQL 语义仍需单独验证。
最后为归档文件计算客户端哈希:
sha256sum evidence/ch02/backup/pg36_shop.dump \
>evidence/ch02/backup/pg36_shop.dump.sha256哈希证明文件字节未变化,不证明内容完整、可信或可恢复。
2.6.2 pg_restore 的清单、选择性恢复与验证
第一步不是恢复,而是检查:
pg_restore \
--list \
evidence/ch02/backup/pg36_shop.dump \
>evidence/ch02/backup/pg36_shop.list清单列出 pre-data、data、post-data 阶段的模式、表、数据、约束、索引和 ACL 等条目。可以复制清单,按行前加分号排除对象,再用 --use-list 恢复;但手工删除依赖条目可能得到不完整数据库。
还可以把归档展开为 SQL 供审查:
pg_restore \
--file=evidence/ch02/backup/preview.sql \
evidence/ch02/backup/pg36_shop.dump这一步非常重要:恢复来自不可信服务器的 dump,会在目标执行源端超级用户能够植入的任意代码。局部过滤不会消除这一风险。未知来源归档必须先审查,并在严格隔离与最小权限环境处理。
恢复到隔离数据库
不要对源数据库使用 --clean 试验恢复。创建一个名称固定、用途明确的临时数据库:
psql -X -w \
"service=pg36-admin dbname=postgres" \
-v ON_ERROR_STOP=1 <<'PSQL'
SELECT NOT EXISTS (
SELECT 1
FROM pg_catalog.pg_database
WHERE datname = 'pg36_restore'
) AS restore_name_available
\gset
\if :restore_name_available
CREATE DATABASE pg36_restore TEMPLATE template0;
\else
\warn '[restore] refused: database pg36_restore already exists'
DO $restore_error$
BEGIN
RAISE EXCEPTION 'restore target name is already in use';
END
$restore_error$;
\endif
PSQL本章要求从干净 template0 新建。若同名数据库已经存在,上面的保护会返回非零;先确认它是否属于早先演练,再选择单独清理或更换名称,绝不自动覆盖。
恢复:
pg_restore -w \
--exit-on-error \
--single-transaction \
--no-owner \
--no-privileges \
--dbname="service=pg36-admin dbname=pg36_restore application_name=pg36-ch02-restore" \
evidence/ch02/backup/pg36_shop.dump \
>evidence/ch02/backup/restore.stdout \
2>evidence/ch02/backup/restore.stderr--exit-on-error避免默认“继续恢复、最后报告错误数量”的行为;--single-transaction保证本次小型恢复要么全部提交、要么全部回滚,并隐含 exit-on-error;--no-owner --no-privileges让实验不依赖源角色,把对象归当前恢复角色所有;- 大型归档可能因锁数量、事务长度而不适合单事务,生产方案必须实测。
--jobs 能并行装载数据和创建部分对象,但不能与 --single-transaction 同用。并行恢复的成功条件仍是状态验证,不是“worker 都退出了”。
用语义摘要验证
分别在源与恢复库执行:
SELECT
count(*) AS row_count,
min(fixture_id) AS min_id,
max(fixture_id) AS max_id,
md5(
string_agg(
fixture_id || '|' || sku || '|' || payload,
E'\n'
ORDER BY fixture_id
)
) AS checksum
FROM shop.ch02_fixture;期望两边均为 100、1、100 和 00ed4599a6ed75e4441f5211909480fa。再验证列、约束和索引,而不是只查行数:
\d+ shop.ch02_fixture因为本次使用 --no-owner --no-privileges,owner 与 ACL 应与源库不同;这不是失败,而是任务选择的恢复语义。验证报告必须明确哪些属性要求相同、哪些有意重映射。
完成后,删除 pg36_restore 是 R2·破坏性演练。必须先确认它只属于本实验、终止范围仅限这个数据库,再携带精确令牌执行:
psql -X -w \
"service=pg36-admin dbname=postgres" \
-v ON_ERROR_STOP=1 \
-v confirm_drop=DROP_PG36_RESTORE <<'PSQL'
SELECT :'confirm_drop' = 'DROP_PG36_RESTORE' AS drop_confirmed
\gset
\if :drop_confirmed
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'pg36_restore'
AND pid <> pg_backend_pid();
DROP DATABASE pg36_restore;
\else
DO $drop_error$
BEGIN
RAISE EXCEPTION 'drop confirmation is required';
END
$drop_error$;
\endif
PSQL不要把清理动作附在默认备份命令后;保留恢复目标供人工验收,确认后再独立清理。
2.6.3 与 ch21《未雨绸缪:备份体系与恢复演练》的边界
本节证明的是“逻辑对象可以导出、检查、恢复和验证”,不是“生产数据已经安全”。两者之间至少还差:
| 生产问题 | 本节是否覆盖 | ch21 要补什么 |
|---|---|---|
| 整个实例的物理恢复 | 否 | 基础备份、WAL 归档、pgBackRest |
| 任意时间点恢复 | 否 | 时间线、恢复目标、PITR 演练 |
| 角色、表空间与配置 | 部分 | 全局对象、参数、扩展包和基础设施清单 |
| RPO / RTO | 否 | 业务目标、备份频率、恢复计时与容量 |
| 保留、异地和不可变副本 | 否 | 仓库、生命周期、加密、访问控制 |
| 自动验证与告警 | 仅手工样例 | 周期性恢复演练、失败告警、证据归档 |
| 大库恢复性能 | 否 | 并行度、网络、磁盘、锁与资源预算 |
| 高可用拓扑重建 | 否 | Patroni、复制槽、服务与成员恢复 |
Pigsty 的生产备份参考实现以 pgBackRest 物理备份与 WAL 归档为核心;逻辑 dump 更适合对象级迁移、审查和辅助恢复。两者不是互相替代的单选题。
一份文件不是备份结论
至少经历以下状态,才能把本节称为一次演练:
flowchart LR
A["源状态已验证"] --> B["dump 成功"]
B --> C["归档哈希已记录"]
C --> D["清单与 SQL 已检查"]
D --> E["隔离目标恢复成功"]
E --> F["数据与结构验证通过"]
F --> G["耗时、错误和差异已记录"]
链路中任一步失败都应保留证据,而不是删除文件重新跑到“看起来成功”为止。
本节验收
- dump 客户端与服务端版本都进入清单;
- custom 归档有 SHA-256,并能由
pg_restore --list读取; - 恢复前查看了清单或展开 SQL;
- 恢复目标与源数据库隔离;
pg_restore遇错即停,恢复后验证结构、行数和校验和;- 报告明确 owner/ACL 是否保留;
- 能列出本节没有覆盖的 RPO、RTO、WAL、保留和异地问题。
参考资料
- PostgreSQL 18:pg_dump
- PostgreSQL 18:pg_restore
- PostgreSQL 18:pg_dumpall
- PostgreSQL 18:客户端程序版本兼容
- Pigsty v4.4:备份与恢复
- ch21《未雨绸缪:备份体系与恢复演练》
上一节:最小 pgbench 工作负载 · 返回本章目录 · 下一节:实战:把人工操作变成可重跑任务 · 查看全书目录 · 查看索引中心