Skip to content

pgsql 恢复数据实战指南 - 技王数据恢复工程师手记

2026-05-09 10:48:35   来源:技王数据恢复

pgsql 恢复数据实战指南 - 技王数据恢复工程师手记 技王数据恢复

www.sosit.com.cn

当我凌晨三点接到那个电话:pgsql 恢复数据的一次生死时速

事情是这样的——上周四凌晨,一个做电商的客户突然在群里@我,声音都变了:“工程师,我们的pgsql数据库突然连不上了,数据全没了,库存表、订单表全都查不出来……” www.sosit.com.cn

我当时第一反应是:千万别慌,先判断故障类型。很多同行一听到“数据全没了”就急着开始跑恢复脚本,其实很容易二次破坏。我让他先试了试 psql -U postgres 能不能连上,他反馈说连接超时。又问了磁盘空间,他说服务器还有300G空余。好,初步排除磁盘满导致PostgreSQL自杀。再让他检查 pg_xlog 目录(PG 10之前叫 pg_xlog,之后叫 pg_wal),他说这个目录里有些文件看起来是正常的,但有些文件名后面带 .backup.done。这就有意思了,可能是一部分WAL日志被清理了,但归档又没做完整。

www.sosit.com.cn

故障判断:先搞清楚“数据没了”是真的没,还是索引坏了

很多pgsql恢复数据场景,其实不是数据物理消失,而是系统表或索引损坏导致无法访问。比如客户说“查不到订单表”,但可能表本身还在,只是 pg_class 里的记录被写坏了。我们下一步是让客户用 pg_controldata 看控制文件状态。他截了个图给我,控制文件显示 Database cluster state: in production Latest checkpoint's redo location: 0/XXXXXXX 明显落后于当前WAL位置。这暗示了非正常关机或者崩溃后检查点没来得及推进。 www.sosit.com.cn

我让他先不要重启PostgreSQL服务。因为如果数据目录里的关键文件比如 base/ 下的表数据文件其实完整,只是WAL重放没完成,那直接停服务再重启会触发崩溃恢复,有可能把部分未提交的事务丢进去。正确的做法是:先备份整个数据目录,用 pg_stop_backup 或直接冷备份(如果服务已经停了就 rsync)。客户说服务已经自动停止响应了,那我就让他用 systemctl stop postgresql 彻底停掉,然后把 /var/lib/pgsql/data 完整拷贝一份到的盘,再启动服务看看能不能自动恢复。

技王数据恢复

启动后报错:PANIC: could not find relation mapping for OID 16413

果然,重启之后 pg_ctl start 报了这个错误。这个 relation mapping 文件(一般叫 pg_filenode.map)丢失或被破坏了。常见原因包括文件系统损坏、意外断电、或者某些不规范的备份还原操作。这时候如果直接用 pg_resetwal 去重置WAL,很可能会丢失一部分已提交的数据,而且无法保证一致。我决定走另一条路: www.sosit.com.cn

  • 尝试从物理文件里通过 pg_oid 联合 pg_class 系统表直接扫描数据页。这需要 pg_filedumpods 工具,但在远程服务器上不方便。我让他先用 psql 连上 template1 数据库(如果能连上的话),查询 SELECT * FROM pg_class WHERE relname = 'orders' 的 OID。但他连 template1 也报错,说系统表有问题。
  • 另一种办法:直接用文本编辑器查看 base/ 目录下的 global/pg_control?其实不能直接看,但我们可以用 pg_controldata 的输出。但最关键的是我想确认那个丢失的 OID 16413 到底是什么表。好在客户之前做过 pg_dump 的备份,只是一周前的。我让他把旧备份导出来,对比一下 OID 映射。

对比发现,那个 OID 16413 对应的正是订单表的 TOAST 表 —— 也就是存储大字段(比如商品图片URL、备注)的附属表。丢失了 TOAST 表,会导致主表数据能查到但大字段变成 NULL 或报错。这时候单纯的 pgsql 恢复数据 方案就不能只靠数据库内置工具了,需要手动重建 TOAST 指针。

www.sosit.com.cn

我们团队曾经处理过类似案例,那次我们用了技王数据恢复内部开发的一套扫描引擎,直接从表数据文件里提取出原始的 TOAST 指针,然后通过解析 WAL 中残留的插入记录补全内容。但这次客户环境不允许安装第三方工具,我就只能教他用原始方法:先 CREATE TABLE orders_new (LIKE orders INCLUDING ALL); 然后尝试把 orders 表里没有问题的字段(比如订单号、金额)复制过去,大字段用 NULL 占位,再把一周前的 dump 里的大字段通过关联时间戳补进去。

经验案例:一次因为“pg_rewind”用错而导致的二次灾难

说起这个,我想起之前另一个客户,他是在做流复制的主备切换时,用 pg_rewind 想快速把新主库重新同步到旧备库,结果操作失误把目标写反了,导致两个节点都挂掉。那个场景下 pgsql 恢复数据 的核心难点在于:两个节点的 timeline 不一致,常规的 pg_basebackup 又因为时间线分歧拒绝拷贝。我们当时先通过 pg_control 文件里的 timeline_id 判断哪个节点的历史更长,然后手动拷贝数据目录里缺少的 WAL 段,再用 pg_resetwal --milestone 设置一个合理的 checkpoint 点。

数据成功恢复,但丢了一小部分事务(大概几分钟的订单),因为那部分WAL在旧主库上已经被覆盖了。这也是为什么我一直强调:归档日志一定要开启,哪怕只保留3天,在关键时刻能救命。

核心操作步骤:手把手教你安全恢复 pgsql 数据(非专家慎用)

下面我写一个通用的应急流程,但每个案例都要根据具体情况调整——不要死搬硬套

  1. 停止一切写操作:立刻停掉应用层所有写入连接,如果数据库还能连,先 SET default_transaction_read_only = on;,然后执行 SELECT pg_block_backup_start('emergency'); 锁定备份。
  2. 备份数据目录:用 tar czf backup-date.tgz /var/lib/pgsql/data,然后把备份拷贝到独立存储(比如另一台机器或云存储)。
  3. 检查数据库能否启动:执行 pg_ctl start -D /var/lib/pgsql/data -l logfile,看日志输出。如果启动失败记下错误信息,比如 could not open file "base/16384/16413" 就说明具体文件缺失。
  4. 根据错误类型选择恢复策略
    • 如果是关系映射损坏,尝试用 pg_resetwal -f 强行重置WAL(会丢失未提交事务)——要提前确认用户能接受。
    • 如果是某个数据文件损坏且没有备份,可以尝试用 zero_damaged_pages 参数跳过损坏页(但会造成部分行丢失)。
    • 如果是事务日志缺失,需要从归档或从库拷贝WAL段放入 pg_wal 目录。
  5. 使用单用户模式修复:如果数据库能启动但某些表无法访问,用 postgres --single -D /var/lib/pgsql/data template1 进入单用户模式,执行 REINDEX DATABASE yourdb;VACUUM FULL;
  6. 导出导入:如果所有尝试都失败,至少尝试用 pg_dumpallpg_dump 把结构和不损坏的数据导出来,重建实例后再导入。即使部分表报错,也可以加 --exclude-table 跳过。

注意事项:这三个坑我见人踩了无数次

  • 不要在原来的实例上反复尝试启动 — 每次启动失败可能都会写新的WAL,覆盖掉原来的可恢复迹象。
  • 不要直接删除 pg_wal 目录 — 即使你认为所有WAL都不需要,也要至少保留一个检查点之后的那段,否则数据库连启动都没法。
  • 千万不要用文件系统层的删除日志文件的方式“释放空间” — 很多运维觉得数据库日志太大就删了,结果导致WAL不连续,数据库直接挂掉。正确做法是用 pg_archivecleanup 或者设置 wal_keep_segments

又想起一个案例:技王数据恢复帮一位DBA在4小时内救回16TB的pgsql

去年有一个金融客户,他们的归档存储网关突然故障,导致正在写入的WAL文件只写了一部分就断电了。结果主库起不来,报错 WAL file size mismatch。他们技术团队折腾了一天没搞定,找了我们。我们远程过去后,先用 hexdump 看了损坏的WAL文件尾部,发现其实是文件系统层面没有刷新,末尾全是零。我们用 dd 把损坏段文件截断到正常长度(根据前一个段的checksum),然后手动强制重放,再配合 pg_resetwal --restore-target 跳过坏段。最终数据完全无损,因为那些零字节实际上还没有被当作有效WAL写入。

这个过程中我们使用的工具和脚本后来也开源了一部分,但最核心的其实是经验:判断一个WAL文件到底是真的脏了还是只是文件系统缓存没同步。这种事没有固定公式,得看文件系统类型、磁盘类型、是否使用了异步IO等。

结论:pgsql 恢复数据没有银弹,但有一条黄金法则

写到这里,我想说的是——任何关于 pgsql 恢复数据的文章,如果它告诉你“按照这五个步骤一定能恢复数据”,那多半是骗人的。真实的恢复过程更像侦探破案:先收集线索(错误日志、控制文件、数据目录文件列表),再假设,再验证。但有一条黄金法则永远不会错:在做出任何修改之前,先做一份完整的二进制级别备份。备份文件放到安全的地方,哪怕它和你当前的问题无关,当你尝试修复失败后,你还能回到原点重新开始。

再强调一下:pgsql 恢复数据 这件事,70%靠预防(备份+监控+定期的pg_probackup或pgBackRest检验),20%靠故障定位能力,只有10%靠运气。如果自己实在搞不定,别死撑,及时找专业团队——像我们这样常年跟pg各种诡异故障打交道的工程师,往往能省下你很多时间。比如技王数据恢复在PostgreSQL领域深耕了七八年,处理过十几个TB级别的全量丢失案例,最长的恢复周期也就三天。

希望这篇文章能帮到正在被数据库问题折磨的你。如果你有任何关于pgsql恢复数据的具体问题,欢迎留言或者直接来找我——但记得先做好备份。

Back To Top
Search