walminer

简介

walminer 可以解析 wal 日志,尽可能多的获取有用信息,进一步生成undo语句,实现恢复删除或丢失的数据。 功能如下:

  • 从wal日志中提取DML和少部分DDL,并生成对应的回滚语句;

  • 磁盘部分损坏或执行了TRUNCATE等清除操作时,可以使用walminer尝试从wal中提取数据,尽最大可能找回数据。不保证一定能恢复成功。

前提要求

  • 表结构需要尽可能完整,例如字段名称,字段类型等

  • wal日志文件越多越好

注意事项

  • 不建议在生产环境直接执行本节的所有操作。

  • 建议将当前所有文件复制到一个独立的机器进行恢复操作,避免对当前环境造成更大的数据损坏。

  • 在将结果恢复到生产环境时,建议先备份生产环境当前的所有数据。

  • 在一次解析开始时会首先创建或truncate walminer_contents表,当得到想要的数据时建议先导出备份它们再执行第二次解析。

安装/启用

udb-tx-v22.4.22 及以后的版本,已启用 walminer, 直接使用即可。

udb-tx-v22.4.22 及以前的版本,按以下步骤安装启用。

安装

联系技术支持获取并安装。

启用

  • walminer.so 放到安装目录的lib/ 下

  • walminer.control和walminer–3.0.sql 放到安装目录的share/extension/ 下

  • chown -R udb.udb 安装目录/lib/walminer.so

  • chown -R udb.udb 安装目录/share/extension/walminer.*

验证安装

[udb@0a2593d70d5b udb]$ ud_sql 
ud_sql (22.4)
Type "help" for help.
unvdb=# create extension walminer;
-- 创建扩展插件
CREATE EXTENSION
unvdb=# 

使用说明

基础操作

create extension walminer; -- 创建扩展
select walminer_wal_add('/data/udb/ud_wal'); --添加要解析的wal日志目录或单个文件名
select walminer_wal_remove('/opt/test/wal'); --移除wal日志目录或单个文件名
select walminer_wal_list(); --列出wal文件

解析操作

#普通解析
select walminer_all(); --解析已添加的全部wal日志
select walminer_by_time(starttime, endtime); --在add的wal日志中查找对应时间范围的wal记录
select walminer_by_lsn(startlsn, endlsn); --在add的wal日志中查找对应lsn范围的wal记录
select walminer_by_xid(xid); --在add的wal日志中查找对应xid的wal记录
#精确解析
select walminer_by_time(starttime, endtime,'true');  --精确查找对应时间范围的wal记录
select walminer_by_lsn(startlsn, endlsn,'true');  --精确查找对应lsn范围的wal记录
select walminer_by_xid(xid,'true'); --精确查找对应xid的wal记录
#普通解析会严格匹配给定的范围,但可能没找到之前的checkpoint点,所以会出现记录不全的情况;
#精确解析会自动查找给定范围之前的checkpoint点,更完美的解析出结果。当普通解析结果不全时可以尝试精确解析。
#当本机没有数据结构时,可以加载其它机器生成的的数据结构
select walminer_build_dictionary('/tmp/store_dictionary'); --根据当前数据库结构生成数据字典
select walminer_load_dictionary('/tmp/store_dictionary'); --加载数据字典
#reloid 参数可以查找指定表,要用表的oid
select walminer_by_time(starttime, endtime,'false',reloid); --非精确模式查找对应时间范围的reloid表oid的wal记录
select walminer_by_lsn(startlsn, endlsn,'true',reloid); --在add的wal日志中查找对应lsn范围的wal记录
select walminer_by_xid(xid,'true',reloid);--在add的wal日志中查找对应xid的wal记录
#当表结构不完整时创建临时表并使用临时表映射解析。
create table tmp_avatar(i int); --创建临时表
select walminer_table_avatar(tmp_avatar, missed_relfilenode); --映射到临时表。此时的解析结果将按临时表格式输出。
select wal2sql(); --执行解析,wal文件越多,需要的时间也越多。
select * from walminer_contents; --查看解析结果
-- 表walminer_contents 
(
 sqlno int,         --本条sql在其事务内的序号
 xid bigint,        --事务ID
 topxid bigint,        --如果为子事务,这是是其父事务;否则为0
 sqlkind int,        --sql类型1->insert;2->update;3->delete(待优化项目)
 minerd bool,        --解析结果是否完整(缺失checkpoint情况下可能无法解析出正确结果)
 timestamp timestampTz, --这个SQL所在事务提交的时间
 op_text text,        --sql
 undo_text text,    --undo sql
 complete bool,        --如果为false,说明有可能这个sql所在的事务是不完整解析的
 schema text,        --目标表所在的模式
 relation text,        --目标表表名
 start_lsn pg_lsn,    --这个记录的开始LSN
 commit_lsn pg_lsn    --这个事务的提交LSN
)
select walminer_stop(); --结束分析操作,并释放内存。

实践演示

delete 操作恢复

模拟删除操作

waltest=# select * from tb1;         
 id | name 
----+------
  1 | a
  2 | b
(2 rows)
waltest=# delete from tb1;
DELETE 2
waltest=# select * from tb1;
 id | name 
----+------
(0 rows)

配置walminer

waltest=# create extension walminer;--创建扩展
CREATE EXTENSION
waltest=# select walminer_wal_add('/data/udb/ud_wal');--添加wal目录
  walminer_wal_add   
---------------------
 24 file add success
(1 row)
waltest=# select walminer_wal_list();--列出wal文件
              walminer_wal_list              
---------------------------------------------
 (/data/udb/ud_wal/00000001000000090000001C)
 (/data/udb/ud_wal/00000001000000090000001D)
 (/data/udb/ud_wal/00000001000000090000001E)
 (/data/udb/ud_wal/00000001000000090000001F)
 (/data/udb/ud_wal/000000010000000900000020)
 (/data/udb/ud_wal/000000010000000900000021)
 (/data/udb/ud_wal/000000010000000900000022)
 (/data/udb/ud_wal/000000010000000900000023)
 (/data/udb/ud_wal/000000010000000900000024)
 (/data/udb/ud_wal/000000010000000900000025)
 (/data/udb/ud_wal/000000010000000900000026)
 (/data/udb/ud_wal/000000010000000900000027)
 (/data/udb/ud_wal/000000010000000900000028)
 (/data/udb/ud_wal/000000010000000900000029)
 (/data/udb/ud_wal/00000001000000090000002A)
 (/data/udb/ud_wal/00000001000000090000002B)
 (/data/udb/ud_wal/00000001000000090000002C)
 (/data/udb/ud_wal/00000001000000090000002D)
 (/data/udb/ud_wal/00000001000000090000002E)
 (/data/udb/ud_wal/00000001000000090000002F)
 (/data/udb/ud_wal/000000010000000900000030)
 (/data/udb/ud_wal/000000010000000900000031)
 (/data/udb/ud_wal/000000010000000900000032)
 (/data/udb/ud_wal/000000010000000900000033)
(24 rows)
waltest=# select walminer_all(); --提取wal日志
NOTICE:  Switch wal to 00000001000000090000001C on time 2023-03-27 13:58:30.446893+08
NOTICE:  Switch wal to 00000001000000090000001D on time 2023-03-27 13:58:30.447915+08
NOTICE:  Switch wal to 00000001000000090000001E on time 2023-03-27 13:58:30.44841+08
    walminer_all     
---------------------
 ud_minerwal success
(1 row)
waltest=# select * from walminer_contents; --查看提取结果
 sqlno | xid  | topxid | sqlkind | minerd |           timestamp           |              op_text              |                    undo_text                    | complete | schema | relat
ion | start_lsn  | commit_lsn 
-------+------+--------+---------+--------+-------------------------------+-----------------------------------+-------------------------------------------------+----------+--------+------
----+------------+------------
     1 | 1528 |      0 |       3 | t      | 2023-03-27 13:56:45.925757+08 | DELETE FROM public.tb1 WHERE id=1 | INSERT INTO public.tb1(id ,name) VALUES(1 ,'a') | t        | public | tb1  
    | 9/1C0058F8 | 9/1C005A10
     2 | 1528 |      0 |       3 | t      | 2023-03-27 13:56:45.925757+08 | DELETE FROM public.tb1 WHERE id=2 | INSERT INTO public.tb1(id ,name) VALUES(2 ,'b') | t        | public | tb1  
    | 9/1C0059A0 | 9/1C005A10
(2 rows)
--执行undo_text修复数据
waltest=# INSERT INTO public.tb1(id ,name) VALUES(1 ,'a');
INSERT 0 1
waltest=# INSERT INTO public.tb1(id ,name) VALUES(2 ,'b');
INSERT 0 1
waltest=# select * from tb1;--验证结果                                                                                                    
 id | name 
----+------
  1 | a
  2 | b
(2 rows)
select walminer_stop();--结束分析并释放内存。

DDL操作恢复

注意:ddl删除的列,只能恢复列信息,不能恢复列数据。

模拟删除列操作

unvdb=# \c waltest;
You are now connected to database "waltest" as user "unvdb".
waltest=# select * from tb1;
 id | name | age 
----+------+-----
  1 | a    |   1
  2 | b    |   2
(2 rows)
waltest=# ALTER TABLE "public"."tb1" DROP COLUMN "age";
ALTER TABLE
waltest=# select * from tb1;                           
 id | name 
----+------
  1 | a
  2 | b
(2 rows)

恢复配置

waltest=# CREATE EXTENSION  walminer;
CREATE EXTENSION
waltest=# select walminer_wal_add('/data/udb/ud_wal');
  walminer_wal_add   
---------------------
 11 file add success
(1 row)
waltest=# select walminer_wal_list();
              walminer_wal_list              
---------------------------------------------
 (/data/udb/ud_wal/000000010000000900000029)
 (/data/udb/ud_wal/00000001000000090000002A)
 (/data/udb/ud_wal/00000001000000090000002B)
 (/data/udb/ud_wal/00000001000000090000002C)
 (/data/udb/ud_wal/00000001000000090000002D)
 (/data/udb/ud_wal/00000001000000090000002E)
 (/data/udb/ud_wal/00000001000000090000002F)
 (/data/udb/ud_wal/000000010000000900000030)
 (/data/udb/ud_wal/000000010000000900000031)
 (/data/udb/ud_wal/000000010000000900000032)
 (/data/udb/ud_wal/000000010000000900000033)
(11 rows)
waltest=# select wal2sql_with_ddl(); --开启ddl解析
 wal2sql_with_ddl 
------------------
 t
(1 row)
waltest=# select walminer_by_time('2023-03-27 15:00', '2023-03-27 15:15','true'); --必须使用精确解析
NOTICE:  Switch wal to 000000010000000900000029 on time 2023-03-27 15:12:59.812091+08
NOTICE:  Con not find relfilenode 65692 in dictionary, ignored related records
NOTICE:  Con not find relfilenode 65693 in dictionary, ignored related records
  walminer_by_time   
---------------------
 ud_minerwal success
(1 row)
waltest=# select * from walminer_contents;--检查解析结果
 sqlno | xid  | topxid | sqlkind | minerd |           timestamp           |                     op_text                     |             undo_text             | complete |   schema   |  
 relation   | start_lsn  | commit_lsn 
-------+------+--------+---------+--------+-------------------------------+-------------------------------------------------+-----------------------------------+----------+------------+--
------------+------------+------------
     1 | 1578 |      0 |       4 | t      | 2023-03-27 15:01:03.11249+08  | TRUNCATE TABLE public.tb1                       |                                   | t        | pg_catalog | p
g_class     | 9/291A7EB8 | 9/291AA7E0
     1 | 1583 |      0 |       1 | t      | 2023-03-27 15:09:24.706606+08 | INSERT INTO public.tb1(id ,name) VALUES(1 ,'a') | DELETE FROM public.tb1 WHERE id=1 | t        | public     | t
b1          | 9/291DA7F8 | 9/291DA910
     1 | 1584 |      0 |       1 | t      | 2023-03-27 15:09:27.201411+08 | INSERT INTO public.tb1(id ,name) VALUES(2 ,'b') | DELETE FROM public.tb1 WHERE id=2 | t        | public     | t
b1          | 9/291DA948 | 9/291DA9F8
     1 | 1585 |      0 |       2 | t      | 2023-03-27 15:09:31.79967+08  | UPDATE public.tb1 SET  WHERE id=2               | UPDATE public.tb1 SET  WHERE id=2 | t        | public     | t
b1          | 9/291DA9F8 | 9/291DAA78
     1 | 1586 |      0 |       4 | t      | 2023-03-27 15:10:15.078618+08 | ALTER TABLE public.tb1 DROP COLUMN age          |                                   | t        | pg_catalog | p
g_attribute | 9/291DCB98 | 9/291E0BF0
(5 rows)

修复数据

op_text列显示执行过 ALTER TABLE public.tb1 DROP COLUMN age,使用alter add 把它添加进来即可,但此列的数据无法恢复。