自定义与目录格式
pg_dump 的输出格式一共有四种:plain、custom、directory、tar。日常最多人用的是默认的 plain——它生成一个纯文本 SQL 文件,直观、可读、改起来方便,但它有两个绕不过去的短板:无法选择性恢复(只能整段执行),无法并行(单个文件流)。
custom 与 directory 这两种「归档格式」正是为弥补这两个短板设计的。它们不生成 SQL 文本,而是生成一个带有「对象目录(TOC)」的结构化容器:容器里保存着每个数据库对象的定义与数据,恢复时由 pg_restore 决定「恢复哪些、按什么顺序恢复、并行几个进程」。
理解这两种格式的内部结构(TOC 是什么、目录格式里那些 .dat.gz 文件是什么)能让你在恢复出问题时知道该看哪里,也能让你在做大库选型时做出有依据的决定,而不是凭感觉。
前置知识
建议先阅读:pg_restore 恢复、pg_dump 基础用法
基础概念
四种格式一览
| 格式 | -F 取值 | 输出形态 | 默认压缩 | 可选择性恢复 | 可并行导出 | 可并行恢复 | 恢复工具 |
|---|---|---|---|---|---|---|---|
| 纯文本 | p / plain | 单个 SQL 文件 | ✗ | ✗ | ✗ | ✗ | psql |
| 自定义 | c / custom | 单文件归档 | ✔ gzip | ✔ | ✗ | ✔ | pg_restore |
| 目录 | d / directory | 一个目录 | ✔ gzip | ✔ | ✔ | ✔ | pg_restore |
| tar | t / tar | 单文件 tar | ✗ | ✔ | ✗ | ✔ | pg_restore |
官方对 custom 与 directory 的评价是「最灵活的格式」:允许在恢复时手工选择与重排归档项,支持并行恢复,且默认压缩。其中 directory 是唯一支持并行导出的格式。
plain 格式的边界
pg_dump -Fp -f mydb.sql mydb--
-- PostgreSQL database dump
--
SET statement_timeout = 0;
SET lock_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
CREATE TABLE public.orders ( ... );
COPY public.orders (id, tenant_id, amount) FROM stdin;
1 42 199.00
2 42 88.50
\.它的优点是「所见即所得」:可以用文本编辑器打开、diff、手工修补、也能通过管道直接喂给另一个库:
# 管道直传:从 host1 直接导到 host2 的同名库
pg_dump -h host1 mydb | psql -h host2 mydb但它的边界也很清楚:整个文件是一个不可分割的脚本,没有 TOC,没有对象清单,无法只恢复其中一张表。想要「只找回 orders 表」,只能靠 sed / awk 之类的文本处理去截取 SQL 片段,脆弱且不可靠。
归档格式的核心:TOC
custom / directory / tar 三种归档共享同一个内部设计——它们都是「目录清单(TOC)+ 各对象内容」的容器。TOC 是一份记录,逐条列出:
- 对象的 dumpId(归档内的条目编号);
- 对象的表 OID 与对象 OID;
- 对象类型(
TABLE/TABLE DATA/INDEX/CONSTRAINT/TRIGGER/SEQUENCE SET/BLOB…); - 所属 schema 与对象名;
- 原属主。
pg_restore -l 就是把这份 TOC 打印成人可读的文本:
;
; Archive created at 2026-09-30 10:12:33 CST
; dbname: orders
; TOC Entries: 42
; Compression: zstd
; Dump Version: 1.15-0
; Format: CUSTOM
; Integer: 4 bytes
; Offset: 8 bytes
; Dumped from database version: 18.0
; Dumped by pg_dump version: 18.0
;
;
; Selected TOC Entries:
;
215; 1259 16385 TABLE public orders postgres
216; 1259 16386 TABLE public order_items postgres
217; 1259 16387 SEQUENCE public orders_id_seq postgres
215; 1259 16388 INDEX public orders_pkey postgres
3371; 0 16385 TABLE DATA public orders postgres
3372; 0 16386 TABLE DATA public order_items postgres
3373; 0 16387 SEQUENCE SET public orders_id_seq postgres有了 TOC,pg_restore 才能做到「选择性」与「重排」:
- 选择性:按清单里的行决定恢复哪些对象(
-L),或用-n/-t/-I/-P/-T过滤; - 重排:恢复顺序完全按清单文件中的行序决定,可以手工把某个索引提前。
custom 与 directory 的关系
两者是「同一种归档的两种容器形式」:
| 对比项 | custom | directory |
|---|---|---|
| 容器 | 单个文件 | 一个目录 |
| 内部组织 | 压缩数据块 + 尾部 TOC | 每表一个 .dat.gz + toc.dat |
| 随机访问 | 通过文件内偏移 seek | 直接打开对应文件 |
| 并行导出 | ✗ | ✔ |
| 并行恢复 | ✔ | ✔ |
| 单文件便于传输 | ✔ | ✗(需打包) |
| 增量 / 逐文件处理 | 困难 | 容易(可单独压缩、上传) |
关键术语
| 术语 | 含义 |
|---|---|
| 归档格式 | custom / directory / tar 三种非纯文本输出的统称 |
| TOC | Table of Contents,归档内的对象目录清单 |
toc.dat | 目录格式中保存 TOC 的文件 |
| dumpId | TOC 中每个条目的编号,用于关联与排序 |
| 数据块(data block) | custom 格式中承载表数据 / 大对象的压缩单元 |
-Z | 指定压缩算法与级别(PG16 起支持 gzip / lz4 / zstd) |
| 归档格式演进 | PG16 引入多算法压缩、PG17 引入 --filter 等增强 |
语法与用法
四种格式的生成
# plain:纯文本 SQL
pg_dump -Fp -f mydb.sql mydb
# custom:单文件归档,默认压缩
pg_dump -Fc -f mydb.dump mydb
# directory:目录归档,默认 gzip 压缩,支持并行
pg_dump -Fd -j 8 -f mydb_dir mydb
# tar:单文件 tar,不支持压缩
pg_dump -Ft -f mydb.tar mydb压缩参数 -Z
# 只给级别(沿用默认算法 gzip)
pg_dump -Fc -Z 6 -f mydb.dump mydb
# PG16+:显式指定算法
pg_dump -Fc -Z gzip -f mydb.dump mydb
pg_dump -Fc -Z lz4 -f mydb.dump mydb
pg_dump -Fc -Z zstd -f mydb.dump mydb
pg_dump -Fc -Z zstd:9 -f mydb.dump mydb
# 不压缩
pg_dump -Fc -Z none -f mydb.dump mydb
# 目录格式逐文件压缩
pg_dump -Fd -Z zstd:3 -j 8 -f mydb_dir mydb一个必须记住的例外:tar 格式不支持压缩。给 -Ft 加 -Z 没有意义,需要压缩只能打包后用外部工具处理。
查看归档内容
# 列出 custom 归档的 TOC
pg_restore -l mydb.dump
# 列出 directory 归档的 TOC(指向目录)
pg_restore -l mydb_dir
# 列出 tar 归档的 TOC
pg_restore -l mydb.tar归档转文本
# 把归档还原成等价的 SQL 脚本(便于审阅与手工修改)
pg_restore -f mydb_from_archive.sql mydb.dump
pg_restore --no-owner --no-privileges -f mydb_portable.sql mydb.dump归档 → 文本这一步是单向的、无损的阅读通道,但不能反向:把文本「变回」归档需要重新 pg_dump。
详细说明
plain 格式:什么时候仍然该用它
虽然归档格式更强大,plain 仍有不可替代的位置:
| 场景 | 为什么选 plain |
|---|---|
| 跨数据库产品迁移 | 输出是标准 SQL,可人工改写 |
| 需要 diff 两次备份的差异 | 是文本,diff 直接可用 |
| 需要人工精细化修改(改列名、插入数据) | 可读可编辑 |
| 管道直传另一个库 | 与 psql 组成管道即可 |
| 小库、结构简单的库 | 简单够用,不必引入归档 |
| 需要按行校验 / 抽样 | 文本可 grep |
而它不适合的场景也很明确:大库、需要单表恢复、需要并行。
custom 格式的内部结构
custom 是单文件归档,内部大致由三部分组成:
┌──────────────────────────────────────────┐
│ 文件头(版本、时间、库名、压缩方式…) │
├──────────────────────────────────────────┤
│ 对象数据块 │
│ 表数据块(压缩) │
│ 大对象数据块(压缩) │
│ 对象定义块(压缩) │
│ ... │
├──────────────────────────────────────────┤
│ TOC(条目清单,含各数据块在文件中的偏移) │
└──────────────────────────────────────────┘关键设计是 TOC 放在文件尾部,且记录了每个数据块的偏移量。因此:
pg_restore -l只需读文件尾部就能列出全部对象(很快);- 恢复某张表时,
pg_restore按偏移量 seek 到对应数据块,无需扫描整个文件; - 这也解释了为什么
custom支持并行恢复-j——多个 worker 各自 seek 到不同偏移读不同的块,互不干扰。
代价是:由于要随机访问,custom 不能从管道或标准输入读取(并行恢复时尤其如此)。
directory 格式的内部结构
directory 把一个归档摊开成一个目录:
pg_dump -Fd -f /backup/orders_dir orders
ls -lh /backup/orders_dirtotal 1.3G
-rw-r--r-- 1 postgres postgres 32K toc.dat
-rw-r--r-- 1 postgres postgres 480M 3001.dat.gz
-rw-r--r-- 1 postgres postgres 210M 3002.dat.gz
-rw-r--r-- 1 postgres postgres 95M 3003.dat.gz
-rw-r--r-- 1 postgres postgres 41M blob_16401.dat.gz| 文件 | 内容 |
|---|---|
toc.dat | TOC 清单本体(二进制格式),pg_restore 的入口 |
NNNN.dat[.gz] | 各表的数据文件,编号对应 TOC 中的表 OID |
blob_NNNNN.dat[.gz] | 各大对象的数据文件,编号为对象 OID |
几点使用上的好处:
- 可以对单个表文件做处理。未压缩的归档里,官方明确说明「文件可以用 gzip / lz4 / zstd 工具单独压缩」;
- 天然支持并行导出,因为每个 worker 写各自的文件,不会互相阻塞;
- 便于增量传输。只改了几张表时,可以只重传对应的
.dat.gz; - 便于排查。可以按文件大小直接看出哪张表最大。
-f 指定的目录必须不存在,pg_dump 会自己创建;如果目录已存在会报错。这一点与 plain / custom 覆盖已有文件的行为不同。
tar 格式:容易踩的坑
pg_dump -Ft -f mydb.tar mydbtar 是 directory 的打包形式——解开 tar 就得到一个合法的 directory 归档:
mkdir /tmp/mydb_dir && tar -xf mydb.tar -C /tmp/mydb_dir
pg_restore -l /tmp/mydb_dir # 与直接 pg_restore -l mydb.tar 等价它的两个限制必须记住:
- 不支持压缩,所以体积通常比
-Fc大; - 表数据的相对顺序在恢复时不能改变,因为 tar 是顺序流。这意味着
-j并行恢复在此格式下没有意义。
因此 tar 的实际定位比较窄:需要「单文件 + 与 directory 互通」时才用。日常备份用 -Fc,大库并行用 -Fd。
压缩算法对比与选型
PG16 起 -Z 支持多种算法,选型的核心是「压缩比 vs CPU 开销」的权衡:
| 算法 | 压缩比 | 压缩速度 | 解压速度 | 适用场景 |
|---|---|---|---|---|
none | 1:1 | 最快 | 最快 | 归档会再被外部压缩;CPU 极度紧张 |
lz4 | 低 | 极快 | 极快 | 速度优先,日常备份的默认好选择 |
gzip | 中 | 中等 | 中等 | 兼容性最好,历史默认 |
zstd | 高 | 快 | 极快 | 综合最优,推荐默认 |
zstd:9 及以上 | 很高 | 慢 | 极快 | 冷归档、长期留存 |
一个实用建议:日常备份用 zstd 默认级别或 lz4,长期冷归档用 zstd:9 以上。gzip 只在目标工具链不支持新算法时才需要。
# 日常:速度快、体积合理
pg_dump -Fc -Z lz4 -f /backup/daily.dump mydb
# 月度冷归档:压缩比优先
pg_dump -Fc -Z zstd:12 -f /backup/monthly.dump mydb还有一个历史细节值得知道:早期版本的 custom 格式只有在编译时链接了 zlib 才会压缩,否则生成的归档是不压缩的。所以「同样的命令,不同机器上体积差好几倍」是有可能出现的——排查时先确认归档头中的 Compression 字段。
TOC 条目类型解读
pg_restore -l 输出中的对象类型是排查恢复问题的重要线索:
| 类型 | 含义 | 所在阶段 |
|---|---|---|
TABLE | 表定义(不含索引、约束) | pre-data |
SEQUENCE | 序列定义 | pre-data |
TYPE / DOMAIN / FUNCTION | 自定义类型 / 域 / 函数 | pre-data |
SCHEMA / EXTENSION | schema / 扩展 | pre-data |
TABLE DATA | 表数据(COPY 或 INSERT) | data |
SEQUENCE SET | 序列当前值(setval) | data |
BLOB / BLOBS | 大对象定义与数据 | data |
INDEX | 索引 | post-data |
CONSTRAINT | 主键 / 外键 / 检查约束 | post-data |
TRIGGER | 触发器 | post-data |
RULE | 规则 | post-data |
ACL / COMMENT | 权限 / 注释 | 视对象而定 |
看到恢复后「表在、数据在、但索引不见了」,就应该去 -l 输出里找 INDEX 条目——如果清单里没有,说明导出时就漏了(比如用了 -t 局部导出)。
与 --section 的配合
三种归档格式都可以按阶段恢复,这是它们相对于 plain 的一个实用优势:
# 只恢复结构(表、类型、序列、函数)
pg_restore -d mydb --section=pre-data mydb.dump
# 只恢复数据
pg_restore -d mydb --section=data -j 8 mydb.dump
# 只恢复索引、约束、触发器
pg_restore -d mydb --section=post-data -j 8 mydb.dump这个能力在「先建结构、再由外部程序灌数据、最后补索引」的迁移流程里特别有用——可以只取归档的一部分阶段,其余部分自己控制。
而 plain 格式做不到这一点:它只能整段执行,或者靠文本裁剪硬做。
归档格式的版本兼容
归档文件头里记录了 Dump Version 和 Dumped by pg_dump version。恢复时的兼容规则:
- 归档格式本身是跨架构可移植的(官方明确说明),x86 上导出的归档可以在 ARM 上恢复;
- 但
pg_restore的版本应当不低于生成归档的pg_dump版本,否则可能不认识新版本归档的结构; - 目标服务端的大版本可以高于归档来源(逻辑备份的特性),但低于则不受支持。
# 查看归档的版本信息(-l 输出的头部注释里)
pg_restore -l mydb.dump | head -12;
; Archive created at 2026-09-30 10:12:33 CST
; dbname: orders
; TOC Entries: 42
; Compression: zstd
; Dump Version: 1.15-0
; Format: CUSTOM
; Dumped from database version: 18.0
; Dumped by pg_dump version: 18.0这是排查「归档读不了」的第一步:先看头部信息,再确认服务端与工具版本关系。
大库选型建议
决策路径可以归纳成下面几步:
库有多大 / 恢复目标是什么?
│
├─ 单表恢复、并行恢复是刚需
│ ├─ 需要单文件便于传输 → custom (-Fc)
│ └─ 需要最大并行度 / 增量重传 → directory (-Fd -j)
│
├─ 只做「整库覆盖恢复」,且要求输出可读可改
│ └─ plain (-Fp)
│
├─ 需要与 directory 互通的单文件形式
│ └─ tar (-Ft),但接受无法压缩与顺序限制
│
└─ 数据量过大,逻辑备份窗口已无法接受
└─ 转向物理备份(pg_basebackup / pgBackRest)一个具体的经验阈值:单库数据量超过几百 GB、或备份窗口已经挤占业务时段时,逻辑备份就会成为瓶颈,此时无论选哪种格式都不够,应该转向物理备份或专业备份工具。
实战示例
示例一:同一数据库的四种格式对比
# 依次生成四种格式,观察体积与耗时差异
mkdir -p /tmp/fmt_demo && rm -rf /tmp/fmt_demo/*
echo "=== plain ==="
time pg_dump -Fp -f /tmp/fmt_demo/mydb.sql mydb
ls -lh /tmp/fmt_demo/mydb.sql
echo "=== custom ==="
time pg_dump -Fc -Z zstd:3 -f /tmp/fmt_demo/mydb.dump mydb
ls -lh /tmp/fmt_demo/mydb.dump
echo "=== directory ==="
time pg_dump -Fd -j 8 -Z zstd:3 -f /tmp/fmt_demo/mydb_dir mydb
du -sh /tmp/fmt_demo/mydb_dir
echo "=== tar ==="
time pg_dump -Ft -f /tmp/fmt_demo/mydb.tar mydb
ls -lh /tmp/fmt_demo/mydb.tar=== plain ===
real 1m12.403s
-rw-r--r-- 1 postgres postgres 1.4G mydb.sql
=== custom ===
real 1m08.115s
-rw-r--r-- 1 postgres postgres 118M mydb.dump
=== directory ===
real 0m21.882s
480M /tmp/fmt_demo/mydb_dir
=== tar ===
real 1m05.744s
-rw-r--r-- 1 postgres postgres 1.5G mydb.tar四点结论:plain 与 tar 不压缩,体积最大;custom 压缩后体积最小但耗时与 plain 相当;directory 靠 -j 8 把耗时压到最低,压缩后体积居中。
示例二:目录格式的并行导出与并行恢复
# 导出:8 并行
time pg_dump -h 10.0.0.5 -U postgres -w \
-Fd -j 8 -Z zstd:3 \
-f /backup/orders_dir orders
# 查看目录结构
ls /backup/orders_dir | head3001.dat.zst
3002.dat.zst
3003.dat.zst
blob_16401.dat.zst
toc.dat# 恢复:同样 8 并行
time pg_restore -h new-host -U postgres \
-d orders -j 8 --no-owner \
/backup/orders_dir目录格式让导出速度从「受单进程限制」变成「受磁盘与 CPU 核数限制」,是超大库逻辑备份提速最直接的手段。
示例三:读取 TOC 判断归档是否完整
# 备份后的第一道校验:能否列出 TOC
pg_restore -l /backup/orders.dump > /tmp/toc.txt && echo "OK"
wc -l /tmp/toc.txtOK
52 /tmp/toc.txt# 统计各类对象数量,与源库比对
grep -c 'TABLE DATA' /tmp/toc.txt
grep -c 'INDEX' /tmp/toc.txt
grep -c 'SEQUENCE SET' /tmp/toc.txt 28
61
9-- 源库的对应计数(用于比对)
SELECT count(*) FROM pg_class WHERE relkind = 'r' AND relnamespace = 'public'::regnamespace;
SELECT count(*) FROM pg_indexes WHERE schemaname = 'public';
SELECT count(*) FROM pg_class WHERE relkind = 'S' AND relnamespace = 'public'::regnamespace;示例四:把归档转成可读脚本做审计
# 归档 → 文本
pg_restore --no-owner --no-privileges -f /tmp/orders_readable.sql /backup/orders.dump
# 审阅关键 DDL
grep -n 'CREATE TABLE' /tmp/orders_readable.sql
grep -c 'COPY ' /tmp/orders_readable.sql142:CREATE TABLE public.orders (
163:CREATE TABLE public.order_items (
166:CREATE TABLE public.audit_log (
28# 也可以只转某一个 schema
pg_restore -n app -f /tmp/app_only.sql /backup/orders.dump示例五:目录格式逐文件压缩(未压缩导出后自行处理)
# 先做未压缩的目录导出(可用于后续用不同算法分别压缩)
pg_dump -Fd -Z none -j 8 -f /backup/orders_raw orders
# 用 zstd 逐文件压缩(官方说明未压缩归档的文件可用标准工具压缩)
cd /backup/orders_raw
for f in *.dat; do
zstd -q -T4 -o "${f}.zst" "$f"
done
du -sh /backup/orders_raw1.6G /backup/orders_raw注意事项
tar 格式不支持压缩
# -Z 对 tar 无效,别指望它变小
pg_dump -Ft -Z zstd:9 -f mydb.tar mydb
# 要压缩就换 custom 格式,或对 tar 包外部压缩
pg_dump -Ft -f mydb.tar mydb && zstd mydb.tar目录格式的 -f 目录必须不存在
# 目录已存在会直接报错
mkdir /backup/orders_dir
pg_dump -Fd -f /backup/orders_dir orders # 失败
# 先删或用新目录名
rm -rf /backup/orders_dir && pg_dump -Fd -f /backup/orders_dir orders归档格式不能用 psql 恢复
# 错误
psql -d orders -f /backup/orders.dump
# 正确
pg_restore -d orders /backup/orders.dump归档不能从管道读取并行恢复
# custom / directory 需要随机访问,管道+并行会失败
cat mydb.dump | pg_restore -d orders -j 8
# 正确:给出文件路径
pg_restore -d orders -j 8 mydb.dumpdirectory 与 custom 之间不能直接转换
没有「-Fd 转 -Fc」的原生命令。需要转换只能:重新 pg_dump,或把 directory 打成 tar(tar -cf)得到 -Ft 归档。
归档文件头记录了压缩方式,改后缀名无效
# 把 .dump 改名成 .sql 不会让它变成 SQL 脚本
mv mydb.dump mydb.sql # 仍然要用 pg_restore-Z none 与不加 -Z 是两回事
不加 -Z 时使用该格式的默认压缩(archive 格式通常是 gzip);-Z none 才是明确不压缩。
归档的体积差距可能来自编译选项
历史版本中 custom 格式只有在链接了 zlib 时才会压缩;不同构建机器上同样命令产出的体积可能差别很大。排查体积异常时先看 pg_restore -l 头部的 Compression 字段。
归档可以执行任意 SQL
官方安全警告适用于所有格式:归档在恢复时会执行源端超级用户写入的任意代码。审阅不便的归档,先用 pg_restore -f 转文本检查。
plain 格式的 -c / -C 与归档的行为不一致
-c / -C / -O 只对 plain 生效;归档格式下这些参数会被忽略,必须在 pg_restore 阶段指定。这是「同一个参数在不同格式下语义不同」的典型例子,写脚本时务必区分。
最佳实践
- 日常备份默认选
custom(-Fc)。它同时满足「单文件便于传输」「默认压缩」「支持选择性恢复与并行恢复」,是覆盖面最广的选择。
pg_dump -Fc -Z zstd:3 -f "/backup/${DB}_$(date +%F).dump" "$DB"大库备份选
directory(-Fd -j)。它是唯一支持并行导出的格式,导出耗时可随 CPU 核数下降,同时便于逐文件重传与压缩。压缩算法默认用
zstd,追求速度用lz4,长期冷归档用zstd:9以上,把gzip留给兼容性受限的环境。备份完成后立刻用
pg_restore -l校验。这是成本最低、收益最高的一个检查步骤:
pg_restore -l "$DUMP" > /dev/null || { echo "archive corrupt"; exit 1; }按「恢复需求」而不是「备份习惯」选格式。如果明确知道将来需要单表恢复,就从一开始用
-Fc/-Fd,不要指望事后从plain里截取 SQL。不要用
tar格式做常规备份。它既不能压缩,又受顺序限制,只有「需要与 directory 互通的单文件」时才值得用。目录归档定时打包并计算校验和后再上传,弥补它「不是单文件」的短处:
tar -C /backup -cf orders_dir.tar orders_dir
sha256sum orders_dir.tar > orders_dir.tar.sha256保留格式与参数的元数据。归档自身记录了压缩方式与版本,但「用什么命令生成的」不会自动记录;在备份目录里放一个
manifest.txt或是把命令写进脚本,能极大降低半年后的排障成本。跨版本恢复时先看归档头部的
Dump Version与Dumped by pg_dump version,再确认pg_restore版本不低于它。把格式选择写进备份规范的决策树,避免团队里出现「有人用
-Fp、有人用-Fc、恢复时才发现不能单表恢复」的不一致。不可信归档一律先转文本审阅,这条对所有格式都成立:
pg_restore -f /tmp/review.sql "$DUMP" && less /tmp/review.sql- 数据量超出逻辑备份的合理窗口时,及时切换到物理备份。逻辑备份的格式优化只能提升常数倍速度,改变不了「靠 SQL 重建」的本质;当备份或恢复窗口已无法满足业务要求时,应转向
pg_basebackup/ pgBackRest 这类物理方案。
下一节
继续学习:部分备份(表 / 模式过滤)