只需七步,PG小白也可以用PDU完成专家级的误删数据恢复

                                   前言 假设Postgresql中一张大表被全表误删除了,最快速的恢复方法需要几步? 只要七步    在PDU(Postgresql Data Unloader)工具的帮助下,只要归档的W...

                                   前言


假设Postgresql中一张大表被全表误删除了,最快速的恢复方法需要几步?

只要七步   

在PDU(Postgresql Data Unloader)工具的帮助下,只要归档的WAL文件保存完好,只需要七步,任何技术小白都可以将数据恢复出来。


步骤一:在配置文件中填入数据目录和归档目录。

[13pg@node1 xman]$ ls

         pdu  PGDATA.ini

[13pg@node1 xman]$ cat PGDATA.ini

PGDATA=/home/13pg/data/

ARCHIVE_DEST=/home/13pg/wal_arch/


步骤二:输入命令<b;> 完成初始化。


[13pg@node1 xman]$ ./pdu

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


Copyright © 2024-2025 ZhangChen. All rights reserved.

PDU: Postgresql Data Rescue Tool

-----------------------------------------------------

A dedicated data rescue tool for Postgresql databases.

Supported Version 12-17.

-----------------------------------------------------

For support, contact:

WeChat Public Account: ZhangChen-PDU

Email: 1109315180@qq.com


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

使用前请设置open file为足够大,否则数据导出可能失败


PDU.public=# b;

开始初始化...

 -pg_database:</home/13pg/data/global/1262>

    【postgres】

      -pg_schema:</home/13pg/data/base/13580/2615>

      -pg_class:</home/13pg/data/base/13580/1259>,共52行

      -pg_attribute:</home/13pg/data/base/13580/1249>,共2861行

      模式:

        -->public,0张表

    【alldb】

      -pg_schema:</home/13pg/data/base/234877/2615>

      -pg_class:</home/13pg/data/base/234877/1259>,共1001行

      -pg_attribute:</home/13pg/data/base/234877/1249>,共37393行

      模式:

        -->pgagent,8张表

        -->report,7张表

        -->statistics,9张表

        -->public,658张表

        -->nsd,212张表

        -->hic,55张表

PDU.public=#


步骤三:选择数据库和模式。


PDU.public=# use alldb;

|----------------------------------------|

|          模式             |  表数量    |

|----------------------------------------|

|    pgagent                |  8         |

|    report                 |  7         |

|    statistics             |  9         |

|    public                 |  658       |

|    nsd                    |  212       |

|    hic                    |  55        |

|----------------------------------------|

alldb.public=# set nsd;

|--------------------------------------------------|

|               表名                  |  表大小    |

|--------------------------------------------------|

|    ns_l                             |  134.66 MB |

|    rizh                             |  42.89 MB  |

|    act_                             |  35.52 MB  |

|    chec                             |  6.85 MB   |

|    users                            |  1.00 MB   |

|    basi                             |  744.00 KB |

|    ns_c                             |  688.00 KB |

|    act_re_deployment                |  576.00 KB |

|    check_result_info                |  544.00 KB |

|    act_ge_bytearray                 |  456.00 KB |

|    tran                             |  408.00 KB |

|    basic_doc_index_system           |  400.00 KB |

|    ns_t                             |  360.00 KB |

|    ns_b                             |  344.00 KB |

|    ns_d                             |  336.00 KB |

|    act_hi_varinst                   |  320.00 KB |

|    basic_room_stackinfo             |  304.00 KB |

|    jimu1                            |  280.00 KB |

|    jimu2                            |  272.00 KB |

|    jimu3                            |  224.00 KB |

|--------------------------------------------------|

        仅显示表大小排名前 20 的表名


步骤四:扫描需要恢复的表。

alldb.nsd=# scan xman;


正在扫描表<xman>的删除记录...


开始扫描归档目录

|-起始文件<000000010000000200000003>

|-终点文件<000000010000000200000077>


扫描结束,上述文件包含的时间段为

|-开始时间:2025-02-25 22:32:04.167287 CST

|-结束时间:2025-03-01 19:32:25.401816 CST


读取到最后的日志段: 2/7821CD80:

----------------------------------------------------------------------------------------

| 时间戳:2025-03-01 19:32:25.401816 CST | 事务号:28668 | 待恢复条数:176066          |

----------------------------------------------------------------------------------------

步骤五:选择想恢复的事务号,执行数据文件的恢复。

alldb.nsd=# restore del 176066;

不存在的事务号<176066>

alldb.nsd=# restore del 28668;

开始扫描归档目录

|-起始文件<000000010000000200000003>

|-终点文件<000000010000000200000077>


扫描结束,上述文件包含的时间段为

|-开始时间:2025-02-25 22:32:04.167287 CST

|-结束时间:2025-03-01 19:32:25.401816 CST


读取到最后的日志段: 2/7821CE68:

restore成功,请use restore进行数据导出


步骤六:从恢复出的数据文件中导出数据到csv文件中。

alldb.nsd=# use restore;

|----------------------------------------|

|          模式             |  表数量    |

|----------------------------------------|

|    public                 |  1         |

|----------------------------------------|

restore.public=# \dt;

|--------------------------------------------------|

|               表名                  |  表大小    |

|--------------------------------------------------|

|    xman                             |  42.89 MB  |

|--------------------------------------------------|


        仅显示表大小排名前 1 的表名

restore.public=# unload tab xman;

正在解析表 <xman>. 已解析数据页: 5483, 已解析数据: 176066 条

表名<xman>-<restore/public/294044> 解析完成, 5483 个数据页 ,共计 176066 条数据. 成功 176066 条; 失败【0】条

COPY文件路径为:<restore/public/xman.csv>


日志路径为<log/restore_public_unload_xman_err.txt>


步骤七:将数据导回表中。

[13pg@node1 xman]$ psql alldb

psql (13.10)

Type "help" for help.


alldb=# set search_path to nsd;

SET

alldb=# copy xman from '/home/13pg/xman/restore/public/xman.csv';

COPY 176066

alldb=# select count(*) from xman;

 count

--------

 176066

(1 row)

以上就是PDU对于删除数据恢复的所有操作步骤,不需要用户了解多么深入的Postgresql内核知识,工具会自动扫描归档目录,替用户完成最复杂的那一部分。

使用技巧


步骤很简单,但是我在测试的过程中总结出了一些技巧,结合这些技巧会让你的恢复更加顺利。

1、自定义WAL区间

生产环境下,归档目录里的WAL文件想必不会少,TB级别的也不是不存在。WAL文件越多,扫描的耗时必然越长,因此我提供了参数startWal和endWal让用户可以自定义区间。

用户只需要自己决定要扫描的归档文件区间,在扫描结束后PDU会自动输出该文件区间所对应的事务时间区间,辅助用户直至选择到正确的时间区间为止。

例如我有一张8000000条数据的表需要恢复,但我不知道需要扫描哪些WAL文件,那我就根据删除时间输入一个在这个时间点左右生成的WAL文件作为区间的起始点:同时没有设置终点,那么默认扫描到最后一个文件:

alldb.public=# param startWal 000000010000000700000021;

设置完成,manualSrtWal=000000010000000700000021

alldb.public=# scan xman;


正在扫描表<xman>的删除记录...


开始扫描归档目录

|-起始文件<000000010000000700000021>

|-终点文件<0000000100000007000000C0>


扫描结束,上述文件包含的时间段为

|-开始时间:2025-03-01 22:51:30.382830 CST

|-结束时间:2025-03-01 22:51:30.382830 CST


读取到最后的日志段: 7/C1C3FFC0:

----------------------------------------------------------------------------------------

| 时间戳:2025-03-01 22:51:30.382830 CST | 事务号:21240 | 待恢复条数:7379381         |

----------------------------------------------------------------------------------------

结果扫描出的数据量并没有达到我的预期,于是我将起点往前再调那么点,就得到了预期的8000000条数据。


在这个过程中,用户不需要查看和记录任何烧脑的LSN,只需要看一下大致的时间并输入WAL文件名即可。


alldb.public=# param startWal 000000010000000700000000;

设置完成,manualSrtWal=000000010000000700000000

alldb.public=# scan xman;

正在扫描表<xman>的删除记录...

开始扫描归档目录

|-起始文件<000000010000000700000000>

|-终点文件<0000000100000007000000C0>

扫描结束,上述文件包含的时间段为

|-开始时间:2025-03-01 22:45:15.441775 CST

|-结束时间:2025-03-01 22:51:30.382830 CST

读取到最后的日志段: 7/C1C3FFC0:

----------------------------------------------------------------------------------------

| 时间戳:2025-03-01 22:51:30.382830 CST | 事务号:21240 | 待恢复条数:8000000         |

----------------------------------------------------------------------------------------

2、强制归档与检查点


我们在测试的时候常常会这边删完数据,那边就立马启动PDU开始测试恢复,但是此时delete记录写入的WAL文件很可能分布在pg_wal和归档路径两个位置,导致解析wal时无法连续解析,或者出现一些幽灵数据。


如下所示,这是一张刚刚被删除2000000条记录的全表delete扫描结果:


alldb.public=# scan xman;


SCANNING DELETE RECORDS FOR TABLE <xman>...

start scanning archived walfiles

|-START FILE<0000000100000007000000C6>

|-END FILE<00000001000000080000003D>


PROCESING SCANNING...



SCANNING COMPLETE,time range of walfiles above is

|-SATRT TIME:2025-03-03 20:47:58.471698 CST

|-END TIME:2025-03-03 21:16:09.004600 CST


read until FINAL WAL SEGMENT: 8/3E0B65F8

---------------------------------------------------------------------------------------------

|Timestamp: 2025-03-03 21:16:09.004600 CST | Tx Number: 21591 | Records to restore: 2000000 |

---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------

|Timestamp: 2025-03-03 21:16:09.004600 CST | Tx Number: 21591 | Records to restore: 458336 |

---------------------------------------------------------------------------------------------

这个结果明显有些问题,此时让我们手动强制触发归档


1、先执行select pg_switch_wal()


2、再执行checkpoint,让我们需要解析的WAL日志全部前往归档路径。


再次扫描的时候结果就回到了预期的2000000条记录。


alldb.public=# scan xman;


SCANNING DELETE RECORDS FOR TABLE <xman>...

start scanning archived walfiles

|-START FILE<0000000100000007000000C6>

|-END FILE<00000001000000080000003E>


PROCESING SCANNING...



SCANNING COMPLETE,time range of walfiles above is

|-SATRT TIME:2025-03-03 20:47:58.471698 CST

|-END TIME:2025-03-03 21:17:00.063893 CST


read until FINAL WAL SEGMENT: 8/3F000110

---------------------------------------------------------------------------------------------

|Timestamp: 2025-03-03 21:16:09.004600 CST | Tx Number: 21591 | Records to restore: 2000000 |

---------------------------------------------------------------------------------------------

实现原理


操作说完了,接下来唠唠实现原理。灵感来源于灿灿的这篇文章

这种数据恢复方式,我敢打赌,99%的人没有见过!

我发现在源代码里,--save-fullpage这个功能用到的函数在pg的10-17里都有,因此我直接整合到了PDU中并进行了测试,虽然过程存在波折,但结果还是和预想的一样,可以100%解析出delete的数据。

这种恢复方式本质上就是把每一条delete掉的数据所在的数据页的全页写记录(Full Page Write)解析到同一个数据文件里。

拼凑成一个完整的数据文件后,再用PDU本身的数据文件解析功能把数据unload出来,毕竟PDU一开始设计出来就是为了解析数据文件的。

设计思路

在PDU中,使用命令<b;>初始化后,会生成以用户的业务库名命名的文件夹,例如alldb就是从数据文件中解析出来的数据库名称,当需要解析alldb下的表时,PDU会自动去用户在PGDATA.ini中填入的数据目录中寻找数据文件。

[16pg@node1 xman]$ ls -lsa

总用量 524

  0 drwxrwxr-x.  5 16pg 16pg    122 3月   3 21:40 .

  4 drwx------. 10 16pg 16pg   4096 3月   3 21:38 ..

  0 drwxr-xr-x.  3 16pg 16pg     18 3月   3 21:40 alldb

256 -rwxr-xr-x.  1 root root 261616 3月   3 21:40 pdu

256 -rw-rw-r--.  1 16pg 16pg 261394 3月   3 21:40 pg_class.txt

  4 -rw-rw-r--.  1 16pg 16pg     37 3月   3 21:40 pg_database.txt

  4 -rw-r--r--.  1 root root     58 3月   3 21:40 PGDATA.ini

  0 drwxr-xr-x.  3 16pg 16pg     18 3月   3 21:40 postgres

  0 drwxr-xr-x.  5 16pg 16pg     48 3月   3 21:40 restore

而其中有一个叫做restore的库,这个并非用户自己的业务库,而是PDU自带用于恢复单个数据文件场景的一个库。

[16pg@node1 xman]$ ls -lsa restore/

总用量 0

0 drwxr-xr-x. 5 16pg 16pg  48 3月   3 21:40 .

0 drwxrwxr-x. 5 16pg 16pg 122 3月   3 21:40 ..

0 drwxr-xr-x. 2 16pg 16pg   6 3月   3 21:40 datafile

0 drwxr-xr-x. 2 16pg 16pg  52 3月   3 21:40 meta

0 drwxr-xr-x. 2 16pg 16pg   6 3月   3 21:40 public

用户使用<restore del 事务号>命令后,解析出的数据文件会被放在restore/datafile路径下

---------------------------------------------------------------------------------------------

|Timestamp: 2025-03-03 21:46:03.338753 CST | Tx Number: 21640 | Records to restore: 800000 |

---------------------------------------------------------------------------------------------

alldb.public=# restore del 21640;

start scanning archived walfiles

|-START FILE<0000000100000007000000C6>

|-END FILE<000000010000000800000074>


PROCESING SCANNING...

|-28883 blocks assembled


SCANNING COMPLETE,time range of walfiles above is

|-SATRT TIME:2025-03-03 20:47:58.471698 CST

|-END TIME:2025-03-03 21:46:03.338753 CST


read until FINAL WAL SEGMENT: 8/75000148

restore completed,you can unload data after <use restore>

用户使用use restore切换到restore库后,就可以使用\dt,\d+等命令正常查看恢复出的表文件。

alldb.public=# use restore;

|----------------------------------------|

|          Schema           |  Tab Num   |

|----------------------------------------|

|    public                 |  1         |

|----------------------------------------|

restore.public=# \dt;

|--------------------------------------------------|

|              Tablename              |    Size    |

|--------------------------------------------------|

|    xman                             |  225.65 MB |

|--------------------------------------------------|


        1 tables in total

restore.public=# \d+ xman;

----------------------------------------------------------------

|                            建表语句                           |

----------------------------------------------------------------

   CREATE TABLE xman (

        a                        int,

        b                        varchar(16),

        c                        varchar(32),

        d                        varchar(40),

        d2                       varchar(40),

        e                        varchar(128),

        e2                       varchar(128),

        f                        date,

        g                        int

   );

----------------------------------------------------------------

|                                                              |

----------------------------------------------------------------


最重要的,就是可以像其他业务库一样使用<unload tab 表名>命令将数据导出为csv文件,与其他库里的数据导出方式保持了一致的使用逻辑。


restore.public=# unload tab xman;

Current table <xman>. Numbers of Pages decoded: 28883, Numbers of records decoded: 800000

<xman>-<restore/datafile/168858> Extraction Completed,28883 Pages , 800000 Records Decoded IN TOTAL.SUCCESS: 800000 ;FAILED: 【0】

 Copy File Path:<restore/public/xman.csv>



  • 发表于 2025-08-30 11:15
  • 阅读 ( 11 )

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
shitian
shitian

662 篇文章

作家榜 »

  1. shitian 662 文章
  2. 石天 437 文章
  3. 每天惠23 33 文章
  4. 小A 29 文章