PostgreSQL SQL转储实战:pg_dump备份与恢复全指南

发布时间:2026/10/5 3:29:35
PostgreSQL SQL转储实战:pg_dump备份与恢复全指南 半夜接到电话说测试库里一张核心表的数据被人误清空我当时下意识做的第一件事就是翻当天的SQL转储备份。PostgreSQL的备份和恢复方案有很多种物理备份有pg_basebackup、文件系统快照逻辑备份里最常用、也最容易被低估的就是SQL转储。这个标题看起来简单但它背后的门道一点不比二进制备份少。我接触PG的时间不算短见过太多人因为SQL转储就是把数据库导出一个SQL文件这句话栽在恢复这一步上。SQL转储确实是逻辑备份的核心实现但它牵扯到格式选型、权限处理、版本兼容、恢复顺序等一系列问题。这篇文章不打算讲教科书式的概念而是把我实际用PostgreSQL做SQL转储的经验、踩过的坑、以及恢复时真正需要关注的点一次性讲透。1. SQL转储的核心思路与适用场景1.1 转储的本质把数据库翻译成SQL指令集SQL转储的原理说起来很直白pg_dump连上数据库把库里的表结构、数据、索引、约束、函数、视图、序列、触发器等对象按照依赖关系翻译成一组SQL语句最后写进一个文件里。恢复的时候再把这些SQL语句按顺序执行一遍就能把数据库重新搭建出来。这也是它和物理备份最根本的区别。物理备份拷的是数据文件本身恢复时直接把文件放回数据目录不关心数据库里面存的是什么。SQL转储拷的是如何重建这个数据库的指令恢复时是在全新的数据库实例里重放这些指令。用一个生活化的类比来理解物理备份是把你家的房子整体拍照、复刻出来家具、墙纸、水电管线位置全都还原SQL转储是给你一张装修图纸加一份家具清单你要照着图纸重新装修一遍再按照清单把家具摆回去。后者的文件往往小得多但恢复时更依赖图纸本身的质量。1.2 什么场景下该用SQL转储先明确一点SQL转储并不是万能方案。它适合很多场景但也有些场景你应该绕开它。适合用SQL转储的场景我实际使用中总结下来有这么几类数据库跨版本升级PG的物理文件格式在不同大版本之间不能直接拿来用但SQL转储文件可以。我从PG 13往PG 16迁移数据用的就是SQL转储步骤简单、结果可靠。跨平台迁移从Windows迁到Linux、从物理机迁到云数据库这些场景文件系统布局完全不同物理备份基本用不上SQL转储是首选。单表或部分数据的备份恢复只需要恢复一张被误删数据的表时用SQL转储单独导出这张表比恢复整个实例省太多事。逻辑结构迁移或归档把表结构、约束条件、默认值等逻辑定义抽出来作为文档归档或者在新环境里重建结构。不适合用SQL转储的场景同样要心里有数大数据量全库备份当库已经到几百GB甚至TB级别用SQL转储导出和导入的效率都太慢。这种情况下应该考虑物理备份工具比如pg_basebackup、pgBackRest。需要时间点恢复PITRSQL转储只有某一个时刻的快照没办法做持续归档和任意时间点回放持续归档得靠WAL日志配合。极高频的备份要求每天做多次全库SQL转储在大库上不现实更适合配合WAL归档做增量或者物理方案。1.3 逻辑备份与物理备份的选型建议这里多聊两句备份选型。我在实际项目里通常不把SQL转储和物理备份对立起来而是让它们各干各的。SQL转储适合做定期的逻辑备份用来兜底误操作、满足数据迁移需求物理备份负责做整机的快速恢复配合WAL归档还能实现秒级恢复。不少团队只做SQL转储完全不做物理备份这在遇到数据文件损坏、硬件故障时会很被动。反过来只做物理备份不做SQL转储遇到某张表被误删除这种逻辑层故障时恢复起来也非常痛苦。两者的关系更像是互补而不是替代。2. pg_dump实操从库级全备到单表导出2.1 基础命令与输出格式选择PostgreSQL里做SQL转储的核心工具是pg_dump。最基础的用法是这样pg_dump -h 127.0.0.1 -p 5432 -U postgres -d mydb -f mydb.sql这条命令指定了连接参数和输出文件-d后面跟着库名-f指定输出文件。默认情况下pg_dump的输出是纯文本SQL格式也就是一条条用分号结束的SQL语句。但这里有个很关键的点输出格式的选择会直接影响后续恢复的效率和灵活性。pg_dump支持四种输出格式很多新手根本不知道这回事直接默认导出纯文本后面遇到大库恢复就傻眼了。格式参数特点恢复方式纯文本-Fp可读性强体积略大psql执行自定义归档-Fc压缩率高支持选择性恢复、并行恢复pg_restoretar归档-Ft可解压查看内部文件支持选择性恢复pg_restore目录归档-Fd每张表一个文件适合超大库并行恢复pg_restore我个人最推荐的是自定义归档格式-Fc。它自带压缩文件比纯文本小很多支持pg_restore做选择性恢复比如只捞回一张表还支持--jobs参数做并行恢复速度提升非常明显。2.2 常用参数详解与实测效果pg_dump参数非常多我不打算全列只说几个我实际项目中必用的。-Fc 自定义归档格式pg_dump -h 127.0.0.1 -U postgres -d mydb -Fc -f mydb.dump这是我最常用的全库备份命令。生成的dump文件是二进制格式打开看不到明文SQL但pg_restore能识别。文件压缩率高我实测过一个大概10GB的库导出后的dump文件只有2GB左右差别很大。-t 指定单表导出pg_dump -h 127.0.0.1 -U postgres -d mydb -t public.users -Fc -f users.dump只导出users这张表。这里注意表名最好带上schema前缀避免多个schema下存在同名表时导出不完整。另外-t参数可以指定多张表用逗号隔开但不要把整个schema的表都靠--table来列那样效率很低。整个schema导出应该用--schema参数。-j 并行导出pg_dump -h 127.0.0.1 -U postgres -d mydb -Fd -j 4 -f /backup/mydb_dir-j参数启用并行导出但有个前提输出格式必须是目录格式-Fd且目标路径是目录而非文件。并行导出能明显缩短备份时间我实测过8核机器上从串行的40分钟压到12分钟。不过并行导出对数据库服务器的CPU和I/O有压力生产环境设置并行度时不要贪心。需要注意--jobs在导出时只并行处理表数据元数据部分仍然是单进程。--exclude-table-data 只备份结构不备份数据如果只想导出表结构不想把数据带出来可以用pg_dump -h 127.0.0.1 -U postgres -d mydb --schema-only -f schema.sql或者只排除某几张表的数据保留其他表数据pg_dump -h 127.0.0.1 -U postgres -d mydb -Fc --exclude-table-datapublic.logs -f mydb_no_logs.dump经常遇到一种场景日志表、审计表动辄几千万行全量备份时占了大部分时间真出故障时又用不上这些数据。把它们排除掉备份文件小好几倍备份时间也快很多。我维护的一个业务库排除三张日志表之后备份文件从7GB变成1.8GB时间缩短将近六成。2.3 跨版本导出的版本兼容性问题PG大版本升级场景里SQL转储算是比较稳的路径但版本兼容问题容易在这里翻车。核心规则是pg_dump的版本最好不低于数据库源版本也不高于目标版本。举个例子要在PG 13的库上做逻辑备份准备恢复到PG 16那么执行导出的pg_dump建议是PG 14、15或16自带的版本而不是PG 13自带的。这背后的原理是新版pg_dump对旧版数据库的数据结构理解更全面生成的SQL脚本兼容性更好能避免旧版不认识新对象类型的问题。反过来的场景也要注意如果源库是最新版本目标库是旧版本pg_dump的版本就必须低于或等于目标版本否则导出的SQL脚本里可能带有目标版本不支持的语法。实际操作中我通常用目标版本同款的pg_dump来导出源库两边都稳妥。PG 15以后有些旧版本的SQL转储文件直接恢复会碰到缺失的语法或类型比如某些和版本有关的函数。稳妥的做法是先恢复到中间版本再升级到最终版本虽然步骤多一点但排查问题容易很多。2.4 角色与权限处理经验SQL转储有个让很多人头疼的地方权限和角色问题。pg_dump默认不会导出数据库集群级别的全局对象比如角色、表空间。如果你在dump文件里看到创建了某个用户那是数据库内的对象比如表的所有者触发的。恢复时如果目标库没有对应的角色会直接报错role does not exist。为此PG提供了一个配套工具pg_dumpall它可以转储全局对象pg_dumpall -h 127.0.0.1 -U postgres --globals-only -f globals.sql这个命令只导出角色、表空间不导出数据库和数据。规范的做法是先恢复globals.sql再恢复库级dump文件。没有先恢复全局对象就急着恢复数据库大概率会中途报权限错误然后你花很长时间查明原因——实际上只是缺个角色而已。我自己的备份脚本里会把全局对象单独导出和库的dump文件放在一起。恢复的时候先执行globals.sql再执行库级恢复顺序错一次就够你折腾半天的。3. 恢复实操从SQL文件到pg_restore3.1 纯文本SQL文件的恢复恢复方式取决于备份时选择的格式。如果导出的是纯文本SQL文件恢复手段就是psqlpsql -h 127.0.0.1 -U postgres -d mydb -f mydb.sql这里有个前提目标数据库必须已经创建好pg_dump的纯文本SQL脚本通常不包含CREATE DATABASE语句。所以恢复前要先手动建库createdb -h 127.0.0.1 -U postgres -O myuser mydb-O参数指定数据库的所有者建议和原来保持一致。如果不指定默认所有者是当前执行的用户后面很容易出现权限对不上的怪问题。3.2 pg_restore的灵活恢复单表恢复、list文件、并行恢复如果备份用的是-Fc、-Ft、-Fd格式恢复工具就必须是pg_restore。基础恢复命令pg_restore -h 127.0.0.1 -U postgres -d mydb -Fc mydb.dump单表恢复有时候整库恢复不现实只想恢复一张表。这个用pg_restore非常方便pg_restore -h 127.0.0.1 -U postgres -d mydb -Fc -t public.users mydb.dump只恢复users表的数据和定义。我处理过好几次误删一张表的紧急情况用这个命令几分钟就搞定了完全不用去动整个库。并行恢复pg_restore -h 127.0.0.1 -U postgres -d mydb -Fc -j 4 mydb.dump这里学到一个血泪教训并行恢复对逻辑依赖的处理并不完美。默认情况下-j参数会把对象分成几组并发执行但像表A的外键依赖表B这种关系分组时不一定能自动处理好。遇到依赖问题时恢复日志里会出现外键冲突报错。我的处理方法是初次恢复时不加-j先把主结构恢复好再加--data-only和-j把数据并行灌进去。或者用--section参数把数据段单独提取出来并行恢复。这种方式比单独依赖pg_restore的自动分组可靠得多。list文件查看与选择性恢复-Fc格式的dump文件可以生成一份内容清单看到里面到底有哪些对象pg_restore -l mydb.dump mydb_list.txt这个list文件格式很规整每行前面有编号、类别、schema、对象名。你可以编辑这个文件过滤掉不想恢复的对象再通过-l参数交给pg_restorepg_restore -h 127.0.0.1 -U postgres -d mydb -Fc -l -L mydb_filtered.txt mydb.dump这个玩法的价值在于你可以在一个dump文件里只恢复部分对象而不需要重新导出。我在迁移过程中用过几次效果很好。3.3 恢复到新库的完整流程示例说一个我经常执行的完整恢复流程方便你做参考。假设要将一个PG 13库恢复到PG 16的新实例。首先在PG 13源库上导出全局对象和数据库pg_dumpall -h 127.0.0.1 -p 5432 -U postgres --globals-only -f /backup/globals.sql pg_dump -h 127.0.0.1 -p 5432 -U postgres -d appdb -Fc -f /backup/appdb.dump然后在PG 16新实例上恢复psql -h 127.0.0.1 -p 5432 -U postgres -f /backup/globals.sql createdb -h 127.0.0.1 -p 5432 -U postgres -O appuser appdb pg_restore -h 127.0.0.1 -p 5432 -U postgres -d appdb -Fc -j 4 /backup/appdb.dump这样三步走完基本不会有兼容性问题。需要注意的是如果源库和目标库的扩展版本不同、某些自定义类型或函数是第三方扩展提供的可能还需要在新库先安装这些扩展否则恢复某些对象时会报错。常见的比如postgis、uuid-ossp、pgcrypto这些扩展需要在恢复前用CREATE EXTENSION提前创建。3.4 恢复前必须做的检查清单恢复操作是高危操作我每次恢复前都强制自己过一遍检查清单目标库是否存在且为空如果是恢复到已有数据的库会产生对象冲突。pg_restore在对象已存在时会报错但数据可能已经混进去一部分反而更麻烦。建议先建一个干净的库。磁盘空间是否充足恢复过程需要临时空间而且索引、约束的构建会让数据膨胀一段时间。恢复前用df看磁盘剩余空间至少要预留原库体积的1.5倍以上。角色和扩展是否已创建这个前面提过先恢复全局对象先装扩展避免中途报错。源库版本与目标库版本是否兼容如果跨度特别大宁可分步升级不要指望一次成功。备份文件完整性检验如果dump文件来自网络传输或长期存储先检查文件大小、校验和不要等到恢复了一半才发现文件损坏。4. 常见问题与故障排查实录4.1 用户或角色不存在的报错恢复时最常遇到的错误就是ERROR: role appuser does not exist几乎可以断定是恢复前没有导入全局对象。解决办法很简单先执行globals.sql再来恢复库。如果globals.sql找不到了也可以用CREATE ROLE手动补CREATE ROLE appuser LOGIN PASSWORD your_password;但这里有个坑如果你创建的role和dump文件里保存的角色属性比如SUPERUSER、CREATEDB权限不一致后续对象的权限还是会出错。最可靠的方案永远是先用pg_dumpall导出全局对象。4.2 版本不匹配引发的错误恢复旧版本库到新版本时常见错误包括ERROR: function pg_catalog.version() does not exist ERROR: syntax error at or near WITH前者往往是有些旧版本的函数被标为废弃新版本里被移除后者是SQL脚本里带了一些旧版本特有的语法语法解析直接失败。排查思路是看错误信息里提到的对象名在目标库里查询系统表确认它是否还存在SELECT proname, pronargs FROM pg_proc WHERE proname version;如果不存在的函数被业务对象引用就需要手工调整dump文件把对应语句注释掉再重新恢复。还有一种更省心的做法是在源库先把这些残留对象清理干净再导出。4.3 恢复中断、磁盘空间不足的处理恢复大库时我碰到最多的中断原因是磁盘空间不够。特别是并行恢复时多个进程同时写入临时表、排序文件空间消耗速度比预想中快得多。我见过有人恢复一个60GB的库时预留了90GB结果中途还是爆了磁盘。原因在于索引构建时临时文件加索引本身需要双倍空间。处理办法恢复前尽量扩大磁盘空间或者使用有足够空间的挂载点。如果不确定空间是否够先用--no-index只恢复数据和结构后续再单独重建索引。恢复中断后不要把dump文件当垃圾一样直接重来。先用list文件分析哪些对象已经恢复针对未恢复的部分用--table或--section参数挑出来续跑可以省大量时间。4.4 中文编码、时区、扩展问题PG的编码问题在SQL转储中也经常出现。如果你在备份时库的编码是UTF8恢复时目标库的编码却是SQL_ASCII中文数据就有可能出现乱码。解决办法是建库时显式指定编码createdb -h 127.0.0.1 -U postgres -E UTF8 -T template0 appdb这里用-T template0是刻意绕过template1避免克隆出意外的依赖对象。时区问题相对隐蔽。如果源库的timestamp字段存的是timestamptz类型恢复后显示的时间会因为目标库时区设置不同而变化。备份和恢复时最好把timezone统一设置成同一个区域或者干脆用UTCPGOPTIONS-c timezoneUTC pg_dump ... PGOPTIONS-c timezoneUTC pg_restore ...最后是扩展。恢复过程中遇到type uuid does not exist或operator does not exist这一类的错误码基本就是扩展没装。在目标库里提前执行CREATE EXTENSION再重新运行pg_restore即可。4.5 备份文件损坏的识别与应对SQL转储文件本身也有可能损坏尤其是长时间存储在坏道上、或者网络中断没有完整下载的情况下。纯文本SQL文件损坏恢复时会在某一行报语法错误-Fc格式损坏pg_restore可能会提示file format error或unexpected EOF。我处理这类问题的经验是执行恢复前先看几个信号。文件大小是不是明显小于预期用pg_restore -l能否正常列出对象清单如果连对象清单都列不出来基本可以判断文件坏了。这时如果还有其他时间点的备份直接换备份。如果没有还有一种碰运气的办法-Fc文件内部有分段结构部分数据段损坏时有些对象还是可以恢复出来的。用pg_restore逐表尝试恢复能捞回一点是一点。这个教训告诉我备份文件不仅要生成还要定期做恢复演练。只有真正能恢复的备份才是有效的备份这句话我每次讲PG备份都要重复一遍。5. 备份策略与自动化实现5.1 结合cron实现每日自动SQL转储手动做备份并不可靠真正的生产环境必须用脚本自动化。我在Linux上最常用的就是cron配Shell脚本。脚本核心逻辑很简单#!/bin/bash BACKUP_DIR/backup/pg_dump DATE$(date %Y%m%d_%H%M%S) PG_VERSION16 export PATH/usr/pgsql-${PG_VERSION}/bin:$PATH pg_dump -h 127.0.0.1 -U postgres -d appdb -Fc -f ${BACKUP_DIR}/appdb_${DATE}.dump find ${BACKUP_DIR} -type f -name *.dump -mtime 7 -delete这个脚本做了三件事导出dump文件、按日期命名、清理7天前的旧备份。crontab里配置每天凌晨执行0 2 * * * /opt/scripts/pg_dump_backup.sh /var/log/pg_dump_backup.log 21有一点要特别提醒cron执行时的环境变量和交互式Shell不一样PATH里可能没有PostgreSQL的bin目录。要么在脚本里显式export PATH要么在pg_dump前面写全路径否则cron日志里会出现command not found。5.2 备份文件命名、保留策略与异地副本关于备份保留周期我通常按业务重要性来定。一般业务库保留7天每日全量、4周每周备份就够用金融或核心业务建议每日全量保留14天、每周全量保留8周、每月全量保留12个月。命名规范建议包含库名、日期、备份类型方便快速识别appdb_20250115_daily.dump appdb_20250101_weekly.dump appdb_20241201_monthly.dump异地副本这块SQL转储文件本身是逻辑文本或自定义归档适合传输。我一般会把当天备份推一份到另一个机房或对象存储。一个轻量级的做法是rsync SSHrsync -avz /backup/pg_dump/appdb_${DATE}.dump backupuserremotehost:/backup/pg_dump/注意rsync默认不保留文件的原始权限和属主恢复后如果要还原到原环境要注意权限调整。但sql dump文件本身不需要执行权限影响不大。5.3 校验备份完整性的方法备份文件生成后一定要校验完整性。最直接的校验方式是定期做一次恢复演练。我的做法是每季度租一台配置低一些的机器把最近的备份恢复到上面然后跑一遍核心查询的SQL来验证数据完整性。日常的快速校验也不难可以用pg_restore结合--list查看对象清单确认dump文件结构正常pg_restore -l appdb_20250115_daily.dump | head -20如果对数据校验要求更高可以在备份后顺手生成一个校验文件md5sum appdb_20250115_daily.dump appdb_20250115_daily.dump.md5之后传输或者长期存储时用md5sum -c校验文件是否发生变化。还有一种比较推荐的思路是在业务表里放一个带有固定值和更新时间的基准表备份后单独把这个基准表的值提取出来和上次备份对比确认数据变化符合预期。这样比单纯看文件大小是否变化要靠谱得多。5.4 恢复演练的价值与踩坑记录说到恢复演练我也踩过很经典的坑。有次在做季度恢复演练时发现恢复的库缺少一个业务自定义函数原因是在备份前某个开发手动创建了一个临时函数没有纳入任何版本管理和备份对象清单pg_dump倒是有把它导出来但恢复时因为依赖顺序问题被其他对象冲突跳过了一部分恢复完毕后这个函数就缺了。这让我调整了恢复演练的验证方式不再只是能查出来几条数据就完事而是要把业务侧的关键查询、关键函数、关键存储过程统统跑一遍。代价是多花点时间但好处是能发现备份和恢复链路里隐藏的坑而不是真出事时才暴露。6. 内容总结与个人经验补充写了这么多核心就一句话SQL转储一定是PostgreSQL备份体系里值得认真对待的一环它解决迁移、误删恢复、跨版本升级这类场景的能力物理备份替代不了。我个人在实际项目里会维护一套组合策略每日凌晨跑pg_dump全量SQL转储保留最近7天本地副本同时推一份到异地每季度做一次恢复演练验证转储文件的可用性遇到超大库核心业务表走逻辑备份整个实例走物理备份。这套方案不敢说是最优的但应付过不少生产故障至少心里有底。最后再分享一个小技巧不管用哪种方式备份备份完成后第一时间尝试恢复一次哪怕只是恢复到临时库然后立刻删掉。这个习惯可以让你在真正需要数据时不用赌备份文件是否有效。备份是手段恢复才是目的这句话我每次给团队讲PG备份时都会提。