假设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/
[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进行数据导出
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>
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!