Skip to content

自定义与目录格式 ​

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
tart / tar单文件 tar✗✔✗✔pg_restore

官方对 custom 与 directory 的评价是「最灵活的格式」:允许在恢复时手工选择与重排归档项,支持并行恢复,且默认压缩。其中 directory 是唯一支持并行导出的格式。

plain 格式的边界 ​

bash
pg_dump -Fp -f mydb.sql mydb
sql
--
-- 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、手工修补、也能通过管道直接喂给另一个库:

bash
# 管道直传:从 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 打印成人可读的文本:

text
;
; 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 的关系 ​

两者是「同一种归档的两种容器形式」:

对比项customdirectory
容器单个文件一个目录
内部组织压缩数据块 + 尾部 TOC每表一个 .dat.gz + toc.dat
随机访问通过文件内偏移 seek直接打开对应文件
并行导出✗✔
并行恢复✔✔
单文件便于传输✔✗(需打包)
增量 / 逐文件处理困难容易(可单独压缩、上传)

关键术语 ​

术语含义
归档格式custom / directory / tar 三种非纯文本输出的统称
TOCTable of Contents,归档内的对象目录清单
toc.dat目录格式中保存 TOC 的文件
dumpIdTOC 中每个条目的编号,用于关联与排序
数据块(data block)custom 格式中承载表数据 / 大对象的压缩单元
-Z指定压缩算法与级别(PG16 起支持 gzip / lz4 / zstd)
归档格式演进PG16 引入多算法压缩、PG17 引入 --filter 等增强

语法与用法 ​

四种格式的生成 ​

bash
# 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 ​

bash
# 只给级别(沿用默认算法 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 没有意义,需要压缩只能打包后用外部工具处理。

查看归档内容 ​

bash
# 列出 custom 归档的 TOC
pg_restore -l mydb.dump

# 列出 directory 归档的 TOC(指向目录)
pg_restore -l mydb_dir

# 列出 tar 归档的 TOC
pg_restore -l mydb.tar

归档转文本 ​

bash
# 把归档还原成等价的 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 是单文件归档,内部大致由三部分组成:

text
┌──────────────────────────────────────────┐
│ 文件头(版本、时间、库名、压缩方式…)      │
├──────────────────────────────────────────┤
│ 对象数据块                                │
│   表数据块(压缩)                        │
│   大对象数据块(压缩)                    │
│   对象定义块(压缩)                      │
│   ...                                     │
├──────────────────────────────────────────┤
│ TOC(条目清单,含各数据块在文件中的偏移)  │
└──────────────────────────────────────────┘

关键设计是 TOC 放在文件尾部,且记录了每个数据块的偏移量。因此:

  • pg_restore -l 只需读文件尾部就能列出全部对象(很快);
  • 恢复某张表时,pg_restore 按偏移量 seek 到对应数据块,无需扫描整个文件;
  • 这也解释了为什么 custom 支持并行恢复 -j——多个 worker 各自 seek 到不同偏移读不同的块,互不干扰。

代价是:由于要随机访问,custom 不能从管道或标准输入读取(并行恢复时尤其如此)。

directory 格式的内部结构 ​

directory 把一个归档摊开成一个目录:

bash
pg_dump -Fd -f /backup/orders_dir orders
ls -lh /backup/orders_dir
text
total 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.datTOC 清单本体(二进制格式),pg_restore 的入口
NNNN.dat[.gz]各表的数据文件,编号对应 TOC 中的表 OID
blob_NNNNN.dat[.gz]各大对象的数据文件,编号为对象 OID

几点使用上的好处:

  1. 可以对单个表文件做处理。未压缩的归档里,官方明确说明「文件可以用 gzip / lz4 / zstd 工具单独压缩」;
  2. 天然支持并行导出,因为每个 worker 写各自的文件,不会互相阻塞;
  3. 便于增量传输。只改了几张表时,可以只重传对应的 .dat.gz;
  4. 便于排查。可以按文件大小直接看出哪张表最大。

-f 指定的目录必须不存在,pg_dump 会自己创建;如果目录已存在会报错。这一点与 plain / custom 覆盖已有文件的行为不同。

tar 格式:容易踩的坑 ​

bash
pg_dump -Ft -f mydb.tar mydb

tar 是 directory 的打包形式——解开 tar 就得到一个合法的 directory 归档:

bash
mkdir /tmp/mydb_dir && tar -xf mydb.tar -C /tmp/mydb_dir
pg_restore -l /tmp/mydb_dir   # 与直接 pg_restore -l mydb.tar 等价

它的两个限制必须记住:

  1. 不支持压缩,所以体积通常比 -Fc 大;
  2. 表数据的相对顺序在恢复时不能改变,因为 tar 是顺序流。这意味着 -j 并行恢复在此格式下没有意义。

因此 tar 的实际定位比较窄:需要「单文件 + 与 directory 互通」时才用。日常备份用 -Fc,大库并行用 -Fd。

压缩算法对比与选型 ​

PG16 起 -Z 支持多种算法,选型的核心是「压缩比 vs CPU 开销」的权衡:

算法压缩比压缩速度解压速度适用场景
none1:1最快最快归档会再被外部压缩;CPU 极度紧张
lz4低极快极快速度优先,日常备份的默认好选择
gzip中中等中等兼容性最好,历史默认
zstd高快极快综合最优,推荐默认
zstd:9 及以上很高慢极快冷归档、长期留存

一个实用建议:日常备份用 zstd 默认级别或 lz4,长期冷归档用 zstd:9 以上。gzip 只在目标工具链不支持新算法时才需要。

bash
# 日常:速度快、体积合理
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 / EXTENSIONschema / 扩展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 的一个实用优势:

bash
# 只恢复结构(表、类型、序列、函数)
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 版本,否则可能不认识新版本归档的结构;
  • 目标服务端的大版本可以高于归档来源(逻辑备份的特性),但低于则不受支持。
bash
# 查看归档的版本信息(-l 输出的头部注释里)
pg_restore -l mydb.dump | head -12
text
;
; 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

这是排查「归档读不了」的第一步:先看头部信息,再确认服务端与工具版本关系。

大库选型建议 ​

决策路径可以归纳成下面几步:

text
库有多大 / 恢复目标是什么?
│
├─ 单表恢复、并行恢复是刚需
│   ├─ 需要单文件便于传输 → custom (-Fc)
│   └─ 需要最大并行度 / 增量重传 → directory (-Fd -j)
│
├─ 只做「整库覆盖恢复」,且要求输出可读可改
│   └─ plain (-Fp)
│
├─ 需要与 directory 互通的单文件形式
│   └─ tar (-Ft),但接受无法压缩与顺序限制
│
└─ 数据量过大,逻辑备份窗口已无法接受
    └─ 转向物理备份(pg_basebackup / pgBackRest)

一个具体的经验阈值:单库数据量超过几百 GB、或备份窗口已经挤占业务时段时,逻辑备份就会成为瓶颈,此时无论选哪种格式都不够,应该转向物理备份或专业备份工具。

实战示例 ​

示例一:同一数据库的四种格式对比 ​

bash
# 依次生成四种格式,观察体积与耗时差异
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
text
=== 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 把耗时压到最低,压缩后体积居中。

示例二:目录格式的并行导出与并行恢复 ​

bash
# 导出: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 | head
text
3001.dat.zst
3002.dat.zst
3003.dat.zst
blob_16401.dat.zst
toc.dat
bash
# 恢复:同样 8 并行
time pg_restore -h new-host -U postgres \
  -d orders -j 8 --no-owner \
  /backup/orders_dir

目录格式让导出速度从「受单进程限制」变成「受磁盘与 CPU 核数限制」,是超大库逻辑备份提速最直接的手段。

示例三:读取 TOC 判断归档是否完整 ​

bash
# 备份后的第一道校验:能否列出 TOC
pg_restore -l /backup/orders.dump > /tmp/toc.txt && echo "OK"
wc -l /tmp/toc.txt
text
OK
      52 /tmp/toc.txt
bash
# 统计各类对象数量,与源库比对
grep -c 'TABLE DATA' /tmp/toc.txt
grep -c 'INDEX' /tmp/toc.txt
grep -c 'SEQUENCE SET' /tmp/toc.txt
text
  28
  61
   9
sql
-- 源库的对应计数(用于比对)
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;

示例四:把归档转成可读脚本做审计 ​

bash
# 归档 → 文本
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.sql
text
142:CREATE TABLE public.orders (
163:CREATE TABLE public.order_items (
166:CREATE TABLE public.audit_log (
28
bash
# 也可以只转某一个 schema
pg_restore -n app -f /tmp/app_only.sql /backup/orders.dump

示例五:目录格式逐文件压缩(未压缩导出后自行处理) ​

bash
# 先做未压缩的目录导出(可用于后续用不同算法分别压缩)
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_raw
text
1.6G    /backup/orders_raw

注意事项 ​

tar 格式不支持压缩

bash
# -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 目录必须不存在

bash
# 目录已存在会直接报错
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 恢复

bash
# 错误
psql -d orders -f /backup/orders.dump
# 正确
pg_restore -d orders /backup/orders.dump

归档不能从管道读取并行恢复

bash
# custom / directory 需要随机访问,管道+并行会失败
cat mydb.dump | pg_restore -d orders -j 8
# 正确:给出文件路径
pg_restore -d orders -j 8 mydb.dump

directory 与 custom 之间不能直接转换

没有「-Fd 转 -Fc」的原生命令。需要转换只能:重新 pg_dump,或把 directory 打成 tar(tar -cf)得到 -Ft 归档。

归档文件头记录了压缩方式,改后缀名无效

bash
# 把 .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 阶段指定。这是「同一个参数在不同格式下语义不同」的典型例子,写脚本时务必区分。

最佳实践 ​

  1. 日常备份默认选 custom(-Fc)。它同时满足「单文件便于传输」「默认压缩」「支持选择性恢复与并行恢复」,是覆盖面最广的选择。
bash
pg_dump -Fc -Z zstd:3 -f "/backup/${DB}_$(date +%F).dump" "$DB"
  1. 大库备份选 directory(-Fd -j)。它是唯一支持并行导出的格式,导出耗时可随 CPU 核数下降,同时便于逐文件重传与压缩。

  2. 压缩算法默认用 zstd,追求速度用 lz4,长期冷归档用 zstd:9 以上,把 gzip 留给兼容性受限的环境。

  3. 备份完成后立刻用 pg_restore -l 校验。这是成本最低、收益最高的一个检查步骤:

bash
pg_restore -l "$DUMP" > /dev/null || { echo "archive corrupt"; exit 1; }
  1. 按「恢复需求」而不是「备份习惯」选格式。如果明确知道将来需要单表恢复,就从一开始用 -Fc/-Fd,不要指望事后从 plain 里截取 SQL。

  2. 不要用 tar 格式做常规备份。它既不能压缩,又受顺序限制,只有「需要与 directory 互通的单文件」时才值得用。

  3. 目录归档定时打包并计算校验和后再上传,弥补它「不是单文件」的短处:

bash
tar -C /backup -cf orders_dir.tar orders_dir
sha256sum orders_dir.tar > orders_dir.tar.sha256
  1. 保留格式与参数的元数据。归档自身记录了压缩方式与版本,但「用什么命令生成的」不会自动记录;在备份目录里放一个 manifest.txt 或是把命令写进脚本,能极大降低半年后的排障成本。

  2. 跨版本恢复时先看归档头部的 Dump Version 与 Dumped by pg_dump version,再确认 pg_restore 版本不低于它。

  3. 把格式选择写进备份规范的决策树,避免团队里出现「有人用 -Fp、有人用 -Fc、恢复时才发现不能单表恢复」的不一致。

  4. 不可信归档一律先转文本审阅,这条对所有格式都成立:

bash
pg_restore -f /tmp/review.sql "$DUMP" && less /tmp/review.sql
  1. 数据量超出逻辑备份的合理窗口时,及时切换到物理备份。逻辑备份的格式优化只能提升常数倍速度,改变不了「靠 SQL 重建」的本质;当备份或恢复窗口已无法满足业务要求时,应转向 pg_basebackup / pgBackRest 这类物理方案。

下一节

继续学习:部分备份(表 / 模式过滤)

参考链接 ​