我们说数据恢复是数据库 DBA 在日常数据库维护过程中一项难点技能,也是标志一个人是否达到 DBA 的重要指标。
其他关系型数据库比如常用的 MySQL SQLServer Oracle 等都有一些比较成熟的产品可以直接恢复。但是 PostgreSQL 比较特殊一些,不好直接解析 WAL 日志。或者直接解析也是不可读的。
我们可以通过第三方的一个插件 WALMINER 来解析 WAL 日志,生成逆向的 SQL 语句进行数据误操作的恢复。
创建数据库和对象并初始化数据
CREATE DATABASE data_recovery;
DROP TABLE IF EXISTS public.recovery_test;
CREATE TABLE public.recovery_test (
id bigserial,
cno int8 NOT NULL,
cage int4,
PRIMARY KEY (id));
ALTER TABLE public.recovery_test OWNER TO postgres;
INSERT INTO public.recovery_test (cno, cage) VALUES (508227500950822913, 10);
INSERT INTO public.recovery_test (cno, cage) VALUES (508227500950822914, 20);
INSERT INTO public.recovery_test (cno, cage) VALUES (507936989916569601, 30);
INSERT INTO public.recovery_test (cno, cage) VALUES (507924225600921600, 40);
INSERT INTO public.recovery_test (cno, cage) VALUES (507924225600921601, 50);
INSERT INTO public.recovery_test (cno, cage) VALUES (508227500950822915, 60);
INSERT INTO public.recovery_test (cno, cage) VALUES (507924225600921602, 70);
INSERT INTO public.recovery_test (cno, cage) VALUES (508953287031136257, 80);
......
SELECT * FROM public.recovery_test;
模拟误操作的语句
DELETE FROM public.recovery_test WHERE id >= 5;
配置环境变量
[root@localhost ~]# vim /etc/profile
export PATH=/usr/local/pgsql-12.8/bin:$PATH
安装插件
[root@localhost ~]#
[root@localhost ~]# cd /usr/local/src/
[root@localhost src]# chmod -R 755 ./XLogMiner-master.zip
[root@localhost src]# unzip XLogMiner-master.zip
[root@localhost src]# cd XLogMiner-master/walminer/
修改编译文件中 PG_CONFIG 参数的值
[root@localhost walminer]# vim Makefile
PG_CONFIG = /usr/local/pgsql-12.8/bin/pg_config
[root@localhost walminer]# USE_PGXS=1 MAJORVERSION=12 make
[root@localhost walminer]# USE_PGXS=1 MAJORVERSION=12 make install
/usr/bin/mkdir -p '/usr/local/pgsql-12.8/lib'
/usr/bin/mkdir -p '/usr/local/pgsql-12.8/share/extension'
/usr/bin/mkdir -p '/usr/local/pgsql-12.8/share/extension'
/usr/bin/install -c -m 755 walminer.so '/usr/local/pgsql-12.8/lib/walminer.so'
/usr/bin/install -c -m 644 .//walminer.control '/usr/local/pgsql-12.8/share/extension/'
/usr/bin/install -c -m 644 .//walminer--3.0.sql '/usr/local/pgsql-12.8/share/extension/'
[root@localhost walminer]#
在数据库中安装插件
[root@localhost walminer]# su - postgres
[postgres@localhost walminer]$ psql
postgres=# CREATE EXTENSION walminer;
postgres=# SELECT * FROM pg_available_extensions WHERE name = 'walminer';
postgres=# SELECT * FROM pg_extension WHERE extname = 'walminer';
postgres=# \c data_recovery
data_recovery=# CREATE EXTENSION walminer;
data_recovery=# SELECT * FROM pg_available_extensions WHERE name = 'walminer';
data_recovery=# SELECT * FROM pg_extension WHERE extname = 'walminer';
添加需要解析的 WAL 日志文件
data_recovery=# SELECT walminer_wal_add('/usr/local/pgsql-12.8/data/pg_wal');
移除旧的 WAL 日志文件
data_recovery=# SELECT walminer_wal_remove('/usr/local/pgsql-12.8/data/pg_wal');
列出所有的 WAL 日志文件
data_recovery=# SELECT walminer_wal_list();
执行解析
data_recovery=# SELECT walminer_all();
解析指定时间范围内的 WAL
SELECT walminer_by_time(starttime, endtime, 'true');
SELECT wal2sql(starttime, endtime ,'true');
解析指定 LSN 范围内的 WAL
SELECT walminer_by_lsn(startlsn, endlsn, 'true');
SELECT wal2sql(startlsn, endlsn, 'true');
解析指定 XID 的 WAL
SELECT walminer_by_xid(xid,'true');
SELECT wal2sql(xid,'true');
单表解析,reloid 是表 pg_class 中对象的 oid
解析指定时间范围内的 WAL
SELECT walminer_by_time(starttime, endtime, 'true', reloid);
SELECT wal2sql(starttime, endtime ,'true', reloid);
解析指定 LSN 范围内的 WAL
SELECT walminer_by_lsn(startlsn, endlsn, 'true', reloid);
SELECT wal2sql(startlsn, endlsn, 'true', reloid);
解析指定 XID 的 WAL
SELECT walminer_by_xid(xid,'true', reloid);
SELECT wal2sql(xid,'true', reloid);
解析结果查询
data_recovery=# SELECT * from walminer_contents;
data_recovery=# SELECT * from walminer_contents WHERE op_text LIKE '%recovery_test%';
-[ RECORD 1 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 843
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.071528+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(1 ,508227500950822913 ,10)
undo_text | DELETE FROM public.recovery_test WHERE id=1
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907A6A0
commit_lsn | 0/907A7C0
-[ RECORD 2 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 844
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.072039+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(2 ,508227500950822914 ,20)
undo_text | DELETE FROM public.recovery_test WHERE id=2
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907A7C0
commit_lsn | 0/907A880
-[ RECORD 3 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 845
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.072534+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(3 ,507936989916569601 ,30)
undo_text | DELETE FROM public.recovery_test WHERE id=3
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907A880
commit_lsn | 0/907A940
-[ RECORD 4 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 846
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.072975+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(4 ,507924225600921600 ,40)
undo_text | DELETE FROM public.recovery_test WHERE id=4
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907A940
commit_lsn | 0/907AA00
-[ RECORD 5 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 847
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.073393+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(5 ,507924225600921601 ,50)
undo_text | DELETE FROM public.recovery_test WHERE id=5
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907AA00
commit_lsn | 0/907AAC0
-[ RECORD 6 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 848
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.073788+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(6 ,508227500950822915 ,60)
undo_text | DELETE FROM public.recovery_test WHERE id=6
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907AAC0
commit_lsn | 0/907AB80
-[ RECORD 7 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 849
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.074163+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(7 ,507924225600921602 ,70)
undo_text | DELETE FROM public.recovery_test WHERE id=7
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907AB80
commit_lsn | 0/907AC40
-[ RECORD 8 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 850
topxid | 0
sqlkind | 1
minerd | t
timestamp | 2024-01-29 11:57:05.074527+08
op_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(8 ,508953287031136257 ,80)
undo_text | DELETE FROM public.recovery_test WHERE id=8
complete | t
schema | public
relation | recovery_test
start_lsn | 0/907AC40
commit_lsn | 0/907AD00
-[ RECORD 9 ]---------------------------------------------------------------------------------
sqlno | 1
xid | 852
topxid | 0
sqlkind | 3
minerd | t
timestamp | 2024-01-29 12:57:47.626491+08
op_text | DELETE FROM public.recovery_test WHERE id=5
undo_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(5 ,507924225600921601 ,50)
complete | t
schema | public
relation | recovery_test
start_lsn | 0/90ADD48
commit_lsn | 0/90ADE98
-[ RECORD 10 ]--------------------------------------------------------------------------------
sqlno | 2
xid | 852
topxid | 0
sqlkind | 3
minerd | t
timestamp | 2024-01-29 12:57:47.626491+08
op_text | DELETE FROM public.recovery_test WHERE id=6
undo_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(6 ,508227500950822915 ,60)
complete | t
schema | public
relation | recovery_test
start_lsn | 0/90ADD90
commit_lsn | 0/90ADE98
-[ RECORD 11 ]--------------------------------------------------------------------------------
sqlno | 3
xid | 852
topxid | 0
sqlkind | 3
minerd | t
timestamp | 2024-01-29 12:57:47.626491+08
op_text | DELETE FROM public.recovery_test WHERE id=7
undo_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(7 ,507924225600921602 ,70)
complete | t
schema | public
relation | recovery_test
start_lsn | 0/90ADDD8
commit_lsn | 0/90ADE98
-[ RECORD 12 ]--------------------------------------------------------------------------------
sqlno | 4
xid | 852
topxid | 0
sqlkind | 3
minerd | t
timestamp | 2024-01-29 12:57:47.626491+08
op_text | DELETE FROM public.recovery_test WHERE id=8
undo_text | INSERT INTO public.recovery_test(id ,cno ,cage) VALUES(8 ,508953287031136257 ,80)
complete | t
schema | public
relation | recovery_test
start_lsn | 0/90ADE20
commit_lsn | 0/90ADE98
data_recovery=#
当我们找到对应的 undo_test 之后执行对应的语句,然后再结束 WAL 解析。
结束 WAL 日志的解析
data_recovery=# SELECT walminer_stop();