pg_dump 使用事务数据的一致视图导出一个 PostgreSQL 数据库,同时允许其他会话继续提交变化。它避免了普通逐表复制的问题:前一张表和后一张表不会因为读取时间不同,就混入彼此不一致的已提交状态。但快照只是备份契约的一部分;归档还必须覆盖需要的数据范围,能被兼容工具读取,并且实际恢复成可使用的数据库。
理解快照覆盖什么
导出期间,快照可见的行构成稳定视图。后续提交不会因为某张表读得晚,就逐渐混进同一次导出。并行导出让各 worker 使用同步快照,而不是各自随时读取。并行 dump 要求 directory 格式;custom 格式支持并行恢复,但不支持并行导出。格式选择应根据导出与恢复两边的需求分别判断,不能把两个方向混为一谈。
数据库快照不会自动让整个业务流程原子化。如果应用分两个事务提交批次头和明细,一致快照完全可能合法地落在这两次提交之间。序列状态也不是普通的 MVCC 行数据,外部文件和其他服务更不在数据库快照范围内。恢复需要满足什么业务不变量,必须结合真实事务边界和所需数据源定义,不能只凭“同一快照”推断所有业务状态都完整。
计算负载和依赖
普通读取和写入通常可以在导出期间继续,但 pg_dump 仍获取 ACCESS SHARE 锁,也消耗 CPU、I/O、连接和快照存活时间。冲突的 DDL 可能等待,也可能使导出失败;长快照会阻止某些旧行版本被清理。增加并行 worker 只是用更多并发资源换时间,不会让备份开销消失。应记录事务年龄和线上负载,避免把备份的影响误认为无关故障。
使用备库可以分担读取,但要考虑恢复冲突与复制延迟,而且从备库导出会排除非日志表内容。一次 pg_dump 也不包含集群级角色和表空间。扩展、所有者、权限、排序规则行为及目标配置,都需要通过合适机制纳入恢复计划。把整个库拆成互不协调的单表 dump,可能丢失共同快照,也可能遗漏这些表依赖的对象,不能当成免费的优化。
坏掉的样子
归档目录能够列出,不代表恢复一定成功。目标缺少角色或扩展时,表数据可能只恢复了一部分。应把归档加载到隔离空库,明确处理错误,再验证业务不变量和代表性查询。pg_restore 默认遇到 SQL 错误仍会继续,最后汇总错误数量;只看到最后一条查询有输出是不够的。小型演练可使用单事务恢复获得原子性,但这个模式不能与并行 job 同用。
工具版本是另一条边界。pg_dump 客户端会拒绝比自身主版本更新的服务器,输出主要面向相同或更新的目标版本,不能保证可以加载到更旧的主版本。应核对客户端、源库、目标库、扩展和恢复选项的实际组合,而不是只说“都是 PostgreSQL 所以可以恢复”。即使语法兼容,排序规则或权限差异也可能改变应用行为。
逻辑导出可以满足经测量确认的恢复目标,但不能任意重放到两次导出之间的时刻。物理基础备份加完整保留的 WAL 链,才提供对应的时间点恢复路径。导出间隔是否可接受,取决于允许的数据丢失窗口和恢复时间,不存在“短于一天就一定不行”的统一阈值。必须按实际要求设计留存与恢复链条。
快速验证
把 Bash 代码保存为 dump.sh。它自行创建实验 PostgreSQL 集群,并在退出时删除。可以用非 root 用户运行 PostgreSQL 18 工具,也可以使用下面的隔离容器;命令不发布网络端口,不挂载宿主机数据。两个数据库、归档和成功标记都只存在于实验临时目录及集群中。
docker run --rm --network none --user postgres --entrypoint bash -i \
postgres:18-alpine < dump.sh
#!/usr/bin/env bash
set -euo pipefail
dump_lab=$(mktemp -d /tmp/pgdump-lab.XXXXXX)
cleanup() {
if [ -f "$dump_lab/data/postmaster.pid" ]; then
pg_ctl -D "$dump_lab/data" -m fast -w stop >/dev/null
fi
rm -rf -- "$dump_lab"
}
trap cleanup EXIT
initdb -D "$dump_lab/data" --no-locale --encoding=UTF8 --auth=trust >/dev/null
pg_ctl -D "$dump_lab/data" -l "$dump_lab/server.log" \
-o "-k $dump_lab -p 55432 -c listen_addresses=''" -w start >/dev/null
export PGHOST="$dump_lab" PGPORT=55432 PGDATABASE=source_db
export DUMP_FILE="$dump_lab/source.dump" DUMP_OK="$dump_lab/dump-ok"
createdb source_db
createdb restored_db
psql -X -v ON_ERROR_STOP=1 <<'SQL'
CREATE TABLE customers (id integer PRIMARY KEY, name text);
CREATE TABLE orders (id integer PRIMARY KEY, customer_id integer REFERENCES customers(id));
INSERT INTO customers VALUES (1,'before');
INSERT INTO orders VALUES (10,1);
BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY;
SELECT pg_export_snapshot() AS snapshot_id \gset
\setenv DUMP_SNAPSHOT :snapshot_id
\! psql -X -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO customers VALUES (2,'after'); INSERT INTO orders VALUES (20,2); COMMIT;" && pg_dump --format=custom --snapshot="$DUMP_SNAPSHOT" --file="$DUMP_FILE" && touch "$DUMP_OK"
COMMIT;
SQL
test -f "$DUMP_OK"
pg_restore --list "$DUMP_FILE" > "$dump_lab/archive-list"
pg_restore --exit-on-error --single-transaction --dbname=restored_db "$DUMP_FILE"
counts='SELECT (SELECT count(*) FROM customers), (SELECT count(*) FROM orders)'
test "$(psql -X -Atc "$counts")" = '2|2'
test "$(psql -X -d restored_db -Atc "$counts")" = '1|1'
test "$(psql -X -d restored_db -Atc 'SELECT count(*) FROM orders o LEFT JOIN customers c ON c.id=o.customer_id WHERE c.id IS NULL')" = 0
psql -X -d restored_db -v ON_ERROR_STOP=1 -c \
'SELECT c.name,o.id FROM customers c JOIN orders o ON o.customer_id=c.id'
pg_dump --version
printf 'PASS: source=2|2 restored=1|1 orphan_orders=0\n'
第一个会话导出快照时,库中只有一位客户和一笔订单。在该事务保持打开时,第二个会话提交另一组有关联的数据。pg_dump 明确导入较早快照,所以恢复后计数是 1|1,源库当前计数则为 2|2。关联查询返回名为 before 的客户和十号订单,悬空订单检查结果为零。这样验证的是确定的并发时序,不依赖导出恰好跑得够慢。
PostgreSQL 18.6 上完整恢复与断言均通过。嵌套 shell 只有在写入和 dump 都成功后才创建标记,防止 psql 在外部命令失败后继续运行而掩盖失败。这个实验验证夹具中的快照选择和恢复,不代表已经测出真实应用的恢复时间。生产备份仍需保留实际恢复演练、归档校验值、留存策略与依赖记录,才能让一次成功导出成为可审阅的恢复能力。
来源
- PostgreSQL pg_dump,快照、格式、范围与版本兼容,已核验 2026-09-10。
- PostgreSQL pg_restore,恢复错误处理和事务选项,已核验 2026-09-10。