PostgreSql 数据恢复之 WalMiner

1,799 阅读6分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

领导:昨晚有同事,执行 update 没加条件,误更新了 pg 生产库6万多条数据,花了3个小时才还原回来,我搜了下 WalMiner 日志挖掘可以恢复数据,你研究下。

WalMiner 安装部署

postgres 用户下编译安装
USE_PGXS=1 MAJORVERSION=12 make
USE_PGXS=1 MAJORVERSION=12 make install

gitee.com/movead/XLog…
安装包、readme上面都有,主要做下测试恢复。(表字段多时需要有主键)

构建测试表

syd=# create table user_info (id int,name varchar);
CREATE TABLE

syd=# insert into user_info (id,name) select generate_series(1,10000),'name_'||generate_series(1,10000);
INSERT 0 10000
Time: 107.785 ms

syd=# select * from user_info limit 100;
  id | name 
 ----+------
   1 | name_1
   2 | name_2
   3 | name_3
   4 | name_4
   5 | name_5
   6 | name_6
   7 | name_7
   8 | name_8
   9 | name_9
  10 | name_10
  11 | name_11
  12 | name_12
  13 | name_13
  14 | name_14
  15 | name_15
  16 | name_16
  17 | name_17
  18 | name_18
  19 | name_19
  20 | name_20
  ……
  ……

全表更新数据

syd=# update user_info set name = 'name';
UPDATE 100000
Time: 3045.620 ms (00:03.046)

syd=# select * from user_info limit 20;
 id | name 
----+------
  1 | name
  2 | name
  3 | name
  4 | name
  5 | name
  6 | name
  7 | name
  8 | name
  9 | name
 10 | name
 11 | name
 12 | name
 13 | name
 14 | name
 15 | name
 16 | name
 17 | name
 18 | name
 19 | name
 20 | name
(20 rows)
Time: 0.933 ms

切换下 wal 日志

syd=# select pg_switch_wal();
 0/96376D0
Time: 10.758 ms

WalMiner 添加 wal 日志

--根据更新数据时间找到记录更新操作的归档(可多不可少)
[root@yuan pg_wal]# ll
total 163840
-rw------- 1 postgres postgres 16777216 Jul 13 15:53 000000010000000000000001
-rw------- 1 postgres postgres 16777216 Jul 13 15:53 000000010000000000000002
-rw------- 1 postgres postgres 16777216 Jul 14 16:00 000000010000000000000003
-rw------- 1 postgres postgres 16777216 Aug  9 16:06 000000010000000000000004
-rw------- 1 postgres postgres 16777216 Aug  9 16:06 000000010000000000000005
-rw------- 1 postgres postgres 16777216 Aug  9 16:17 000000010000000000000006
-rw------- 1 postgres postgres 16777216 Aug  9 16:31 000000010000000000000007
-rw------- 1 postgres postgres 16777216 Aug 12 10:53 000000010000000000000008
-rw------- 1 postgres postgres 16777216 Aug 12 11:10 000000010000000000000009
-rw------- 1 postgres postgres 16777216 Aug 12 11:10 00000001000000000000000A
drwx------ 2 postgres postgres        6 Jul  9 18:02 archive_status

--本环境选择添加7、8、9、A号 wal 日志
syd=# create extension walminer;
CREATE EXTENSION
syd=# \dx
                   List of installed extensions
   Name    | Version |   Schema   |          Description           
-----------+---------+------------+--------------------------------
 pglogical | 2.3.4   | pglogical  | PostgreSQL Logical Replication
 plpgsql   | 1.0     | pg_catalog | PL/pgSQL procedural language
 walminer  | 3.0     | syd        | analyse wal to SQL
 
 syd=# select walminer_wal_add('/app/pgsql/data/pg_wal/000000010000000000000007');
select walminer_wal_add('/app/pgsql/data/pg_wal/000000010000000000000008');
  walminer_wal_add  
--------------------
 1 file add success
(1 row)

syd=# select walminer_wal_add('/app/pgsql/data/pg_wal/000000010000000000000008');
select walminer_wal_add('/app/pgsql/data/pg_wal/000000010000000000000009');
  walminer_wal_add  
--------------------
 1 file add success
(1 row)

syd=# select walminer_wal_add('/app/pgsql/data/pg_wal/000000010000000000000009');
  walminer_wal_add  
--------------------
 1 file add success
(1 row)

syd=# select walminer_wal_add('/app/pgsql/data/pg_wal/00000001000000000000000A');
  walminer_wal_add  
--------------------
 1 file add success
(1 row)

解析 wal 日志

syd=# select walminer_all();
NOTICE:  Switch wal to 000000010000000000000007 on time 2021-08-12 11:31:07.258559+08
NOTICE:  Switch wal to 000000010000000000000008 on time 2021-08-12 11:31:07.266243+08
NOTICE:  Switch wal to 000000010000000000000009 on time 2021-08-12 11:31:58.508183+08
NOTICE:  Switch wal to 00000001000000000000000A on time 2021-08-12 11:32:14.171562+08
    walminer_all     
---------------------
 pg_minerwal success
(1 row)

解析日志表中查询验证恢复数据所需要 sql

syd=# select undo_text from walminer_contents where undo_text like 'UPDATE syd.user_info%'limit 100;
                               undo_text                               
-----------------------------------------------------------------------
 UPDATE syd.user_info SET name='name_1' WHERE id=1 AND name='name'
 UPDATE syd.user_info SET name='name_2' WHERE id=2 AND name='name'
 UPDATE syd.user_info SET name='name_3' WHERE id=3 AND name='name'
 UPDATE syd.user_info SET name='name_4' WHERE id=4 AND name='name'
 UPDATE syd.user_info SET name='name_5' WHERE id=5 AND name='name'
 UPDATE syd.user_info SET name='name_6' WHERE id=6 AND name='name'
 UPDATE syd.user_info SET name='name_7' WHERE id=7 AND name='name'
 UPDATE syd.user_info SET name='name_8' WHERE id=8 AND name='name'
 UPDATE syd.user_info SET name='name_9' WHERE id=9 AND name='name'
 UPDATE syd.user_info SET name='name_10' WHERE id=10 AND name='name'
 UPDATE syd.user_info SET name='name_11' WHERE id=11 AND name='name'
 UPDATE syd.user_info SET name='name_12' WHERE id=12 AND name='name'
 UPDATE syd.user_info SET name='name_13' WHERE id=13 AND name='name'
 UPDATE syd.user_info SET name='name_14' WHERE id=14 AND name='name'
 UPDATE syd.user_info SET name='name_15' WHERE id=15 AND name='name'
 UPDATE syd.user_info SET name='name_16' WHERE id=16 AND name='name'
 UPDATE syd.user_info SET name='name_17' WHERE id=17 AND name='name'
 UPDATE syd.user_info SET name='name_18' WHERE id=18 AND name='name'
 UPDATE syd.user_info SET name='name_19' WHERE id=19 AND name='name'
 UPDATE syd.user_info SET name='name_20' WHERE id=20 AND name='name'
 ……
 ……
 
 --行数也正好能对应起来
 syd=# select count(undo_text) from walminer_contents where undo_text like 'UPDATE syd.user_info%';
 count  
--------
 100000
(1 row)

构造脚本恢复数据

--导出脚本
syd=# \pset footer
Default footer is off.
syd=# \pset t
Tuples only is on.
syd=# \o huifu.sql
syd=# select undo_text||';' from walminer_contents where undo_text like 'UPDATE syd.user_info%';

--大概看下脚本内容(主要看头尾别有多余字符)
[postgres@yuan ~]$ ll
total 8128
-rw-rw-r-- 1 postgres postgres 7577791 Aug 12 11:39 huifu.sql
[postgres@yuan ~]$ more huifu.sql 
 UPDATE syd.user_info SET name='name_1' WHERE id=1 AND name='name';
 UPDATE syd.user_info SET name='name_2' WHERE id=2 AND name='name';
 UPDATE syd.user_info SET name='name_3' WHERE id=3 AND name='name';
 UPDATE syd.user_info SET name='name_4' WHERE id=4 AND name='name';
 UPDATE syd.user_info SET name='name_5' WHERE id=5 AND name='name';
 UPDATE syd.user_info SET name='name_6' WHERE id=6 AND name='name';
 UPDATE syd.user_info SET name='name_7' WHERE id=7 AND name='name';
 UPDATE syd.user_info SET name='name_8' WHERE id=8 AND name='name';
 UPDATE syd.user_info SET name='name_9' WHERE id=9 AND name='name';
 UPDATE syd.user_info SET name='name_10' WHERE id=10 AND name='name';
 UPDATE syd.user_info SET name='name_11' WHERE id=11 AND name='name';
 UPDATE syd.user_info SET name='name_12' WHERE id=12 AND name='name';
 UPDATE syd.user_info SET name='name_13' WHERE id=13 AND name='name';
 UPDATE syd.user_info SET name='name_14' WHERE id=14 AND name='name';
 UPDATE syd.user_info SET name='name_15' WHERE id=15 AND name='name';
 UPDATE syd.user_info SET name='name_16' WHERE id=16 AND name='name';
 UPDATE syd.user_info SET name='name_17' WHERE id=17 AND name='name';
 UPDATE syd.user_info SET name='name_18' WHERE id=18 AND name='name';
 UPDATE syd.user_info SET name='name_19' WHERE id=19 AND name='name';
 UPDATE syd.user_info SET name='name_20' WHERE id=20 AND name='name';
 ……
 ……
 
 --确认没问题后执行脚本(虽然关了自动提交,但还是花了20多分钟,后期再研究下能不能加速这个更新过程)
 syd=# \set AUTOCOMMIT off
 syd=# \i huifu.sql
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 UPDATE 1
 ……
 ……
 syd=# commit;
 COMMIT

数据验证

syd=# select * from user_info;
  id | name 
 ----+------
   1 | name_1
   2 | name_2
   3 | name_3
   4 | name_4
   5 | name_5
   6 | name_6
   7 | name_7
   8 | name_8
   9 | name_9
  10 | name_10
  11 | name_11
  12 | name_12
  13 | name_13
  14 | name_14
  15 | name_15
  16 | name_16
  17 | name_17
  18 | name_18
  19 | name_19
  20 | name_20
  ……
  ……