最近帮一个朋友处理数据库迁移,他那边数据量不小,大概几十 GB,使用的是 PostgreSQL。他之前备份用的是 pgdump 的自定义格式,恢复时直接用了 pgrestore,结果卡了半天,进度条根本不动。他急得直跺脚,问我是不是命令有问题。我过去一看,其实命令没毛病,但他犯了个常见错误——恢复前没弄清楚 pgrestore 的工作机制。这玩意儿不是简单的“解压缩”,它背后有一套复杂的逻辑,用不好就容易翻车。

pgrestore 本质上是个智能恢复工具,它不像 psql 那样逐行执行 SQL 脚本,而是能读取自定义格式、目录格式或 tar 格式的备份文件,然后按你指定的方式恢复数据库对象。这意味着你可以选择只恢复某个表、某个索引,甚至只恢复数据而不恢复结构。这种灵活性在日常运维中特别实用,比如只想恢复一张误删的表,或者把数据迁移到另一个数据库集群,pgrestore 能帮你省掉很多手动拆分的麻烦。但前提是你得理解它的参数和运行逻辑,否则就像开着跑车却挂错了挡。
实际使用中最容易翻车的是并行恢复。很多人觉得数据库恢复就该快,于是上来就加个 -j 参数,例如 pgrestore -j 4 -d targetdb backup.dump。理论上并行能提速,但现实往往不尽如人意。PostgreSQL 的恢复过程涉及大量事务日志写入和索引维护,并行线程之间会互相争抢锁资源,尤其是有外键约束的表,恢复顺序一旦错乱,整个过程就会卡住,甚至报错。我见过一个案例,运维小哥加了 -j 8,结果恢复时间反而比串行多了一倍,只能老老实实改回单线程。
还有一个坑是索引和约束的恢复顺序。pgrestore 默认会先恢复表结构,再恢复数据,最后创建索引和约束。这样设计是为了加速数据插入,因为先建索引再插数据会导致每行都更新索引树,效率极低。但如果你用了 --disable-triggers 参数,触发器在数据恢复期间会被禁用,恢复完再重新启用。这个操作看起来聪明,却可能导致数据不一致。比如在生产库里有审计触发器或级联删除逻辑,禁用触发器后恢复的数据可能缺失关联记录。我有个同事就因为没注意这个参数,恢复完后发现关联表少了上万条记录,花了半天时间手动补数据。
说到具体操作,pgrestore 最常用的场景是恢复整个库。基本命令是 pgrestore -d targetdb backup.dump,但有个细节:目标数据库必须提前建好,且编码、表空间等设置要和原库一致。如果直接往一个不存在的库里跑,它会报错退出。所以很多 DBA 在脚本里会先判断数据库是否存在,不存在就 CREATE DATABASE。另外,如果备份文件里包含了表空间路径,而目标服务器上没有对应路径,恢复也会失败。这时可以用 --tablespace 参数重新映射,把数据放到默认表空间里。
对于只想恢复部分对象的场景,pgrestore 提供了 -l 和 -L 参数,能列出备份文件中的对象清单,然后根据清单过滤。比如你备份了整个库,但只想恢复一个叫 orders 的表,可以先执行 pgrestore -l backup.dump > list.txt,然后编辑 list.txt,把 orders 那一行前面的分号去掉,再用 pgrestore -L list.txt -d targetdb backup.dump。这个操作看起来绕,但比写复杂的 SQL 脚本要靠谱得多,尤其是需要恢复几十个表时,手动过滤比写正则表达式省心。
恢复过程中的日志输出也值得留意。默认情况下,pgrestore 会把错误信息打印到标准错误输出,成功操作只显示进度条。如果加上 -v 参数,它会输出每条 SQL 语句的执行情况,这对调试特别有用。有一次我把备份恢复到测试环境,发现某个表的数据量比原库少了一半,打开 -v 日志才发现,原来那个表在备份时因为磁盘空间不足被截断,pgdump 只写了一半数据。如果没有详细日志,这种问题根本查不出来。
想说的是,pgrestore 虽然强大,但不是万能药。它依赖备份文件的完整性,如果备份文件本身损坏,或者备份时用了压缩但压缩工具版本不兼容,恢复大概率会失败。我建议每次备份后都做一次恢复测试,哪怕只恢复到一个临时环境,也能提前发现隐患。另外,生产环境恢复前一定要检查磁盘空间和数据库连接数,别等到恢复到一半才发现空间不足,那才是最尴尬的。数据库恢复这事儿,准备得越充分,翻车的概率就越低,别等到客户打电话才手忙脚乱。


