作者
李传成:中国PostgreSQL分会认证专家
瀚高软件工程师
前言
walminer是一款PostgreSQL的wal日志的解析工具,它可以依据数据字典解析出产生wal日志中隐含的DML语句。第一个版本的xlogminer工具需要wal为logical级别,而且还有表IDENTITY级别的要求。第二个版本的walminer在易用性上不太友好,无法做到精准解析。walminer2.0是第三个版本,这个版本在上一个版本的基础上增强了易用性,可以做到精准解析,并且做了大量的代码重构以期获得更好的扩展性和对PG13的支持。
项目开源地址为:
https://gitee.com/movead/XLogMiner
有兴趣的同学可以下载使用, 目前walminer2.0还处于开发过程,如果大家有好的改进意见可以提issue,顺便记得star~~~
功能增强
版本支持
编译安装
make && make install
export PATH=/h2/pg_install/bin:$PATH进入walminer代码路径
USE_PGXS=1 MAJORVERSION=12 make#MAJORVERSION支持‘9.5’,‘9.6’,‘10’,‘11’,‘12’,‘13’USE_PGXS=1 MAJORVERSION=12 make install
walminer工具使用的标准步骤
postgres=# create extension walminer;CREATE EXTENSIONpostgres=#
postgres=# select walminer_stop();walminer_stop------------------walminer stoped!(1 row)postgres=#
添加wal日志段postgres=# select walminer_wal_add('pg_wal');walminer_wal_add---------------------31 file add success(1 row)-- 或者postgres=# select walminer_wal_add('/h2/pg_walminer_12/data/pg_wal');walminer_wal_add---------------------31 file add success(1 row)-- 或者postgres=# select walminer_wal_add('/h2/pg_walminer_12/data/pg_wal/000000010000000000000072');walminer_wal_add--------------------1 file add success(1 row)查看添加的wal日志段postgres=# select walminer_wal_list();walminer_wal_list-----------------------------------------------------------(/h2/pg_walminer_12/data/pg_wal/000000010000000000000072)(/h2/pg_walminer_12/data/pg_wal/000000010000000000000073)(/h2/pg_walminer_12/data/pg_wal/000000010000000000000074)(/h2/pg_walminer_12/data/pg_wal/000000010000000000000075)(/h2/pg_walminer_12/data/pg_wal/000000010000000000000076)(/h2/pg_walminer_12/data/pg_wal/000000010000000000000077)(/h2/pg_walminer_12/data/pg_wal/000000010000000000000078)(/h2/pg_walminer_12/data/pg_wal/000000010000000000000079)(/h2/pg_walminer_12/data/pg_wal/00000001000000000000007A)...(31 rows)postgres=#移除wal段postgres=# select walminer_wal_remove('/h2/pg_walminer_12/data/pg_wal/00000001000000000000007A');walminer_wal_remove-----------------------1 file remove success(1 row)
postgres=# select wal2sql();NOTICE: Switch wal to 000000010000000000000074 on time 2020-06-17 21:29:08.289991+08walminer_all---------------------pg_minerwal success(1 row)postgres=#
postgres=# select * from walminer_contents;-- 这里不显示结果了postgres=# \d walminer_contentsTable "pg_temp_3.walminer_contents"Column | Type | Collation | Nullable | Default-----------+--------------------------+-----------+----------+---------sqlno | integer | | |xid | bigint | | |topxid | bigint | | |sqlkind | integer | | |minerd | boolean | | |timestamp | timestamp with time zone | | |op_text | text | | |undo_text | text | | |complete | boolean | | |/* 表walminer_contents(sqlno int, --本条sql在其事务内的序号xid bigint, --事务IDtopxid bigint, --如果为子事务,这是是其父事务;否则为0sqlkind int, --sql类型1->insert;2->update;3->delete(待优化项目)minerd bool, --解析结果是否完整(缺失checkpoint情况下可能无法解析出正确结果)timestamp timestampTz, --这个SQL所在事务提交的时间op_text text, --sqlundo_text text, --undo sqlcomplete bool --如果为false,说明有可能这个sql所在的事务是不完整解析的)*/
walminer 2.0 的解析模式
postgres=# select oid, relfilenode,relname from pg_class where relname ='t2';oid | relfilenode | relname-------+-------------+---------90506 | 90506 | t2(1 row)postgres=# truncate t2;TRUNCATE TABLEpostgres=# select oid, relfilenode,relname from pg_class where relname ='t2';oid | relfilenode | relname-------+-------------+---------90506 | 98623 | t2(1 row)postgres=#
select walminer_all();或 select wal2sql();
select walminer_by_time(starttime, endtime);或 select wal2sql(starttime, endtime);
select walminer_by_lsn(startlsn, endlsn);或 select wal2sql(startlsn, endlsn);
select walminer_by_xid(xid);或 select wal2sql(xid);
postgres=# create table t1(i int, j int, k varchar);CREATE TABLEpostgres=# select pg_current_wal_lsn();pg_current_wal_lsn--------------------0/1645F10(1 row)postgres=# select now();now-------------------------------2020-06-17 22:46:07.992138+08(1 row)postgres=# select txid_current();txid_current--------------499(1 row)postgres=# insert into t1 values(1,1,'test_walminer2.0');INSERT 0 1postgres=# insert into t1 values(2,1,'support_walminer2.0');INSERT 0 1postgres=# select pg_current_wal_lsn();pg_current_wal_lsn--------------------0/1646080(1 row)postgres=# select now();now-------------------------------2020-06-17 22:46:08.025568+08(1 row)postgres=# select txid_current();txid_current--------------502(1 row)postgres=#postgres=# select walminer_stop();walminer_stop------------------walminer stoped!(1 row)postgres=# select walminer_wal_add('pg_wal');walminer_wal_add--------------------1 file add success(1 row)postgres=# select walminer_by_time('2020-06-17 22:46:07.992138+08','2020-06-17 22:46:08.025568+08');NOTICE: Switch wal to 000000010000000000000001 on time 2020-06-17 22:48:30.429331+08walminer_by_time---------------------pg_minerwal success(1 row)postgres=# \xExpanded display is on.postgres=# select * from walminer_contents;-- 这里我们不显示结果了postgres=#
postgres=# select walminer_stop();walminer_stop------------------walminer stoped!(1 row)postgres=# select wal2sql('0/1645F10'::pg_lsn,'0/1646080'::pg_lsn);NOTICE: Add wal from current pg_wal directory, do not suggest use this way in produceNOTICE: Switch wal to 000000010000000000000001 on time 2020-06-17 22:53:52.470086+08wal2sql---------------------pg_minerwal success(1 row)postgres=# select * from walminer_contents;-- 这里我们不显示结果了

select walminer_by_time(starttime, endtime,'true');或 select wal2sql(starttime, endtime,'true');
select walminer_by_lsn(startlsn, endlsn,'true');或 select wal2sql(startlsn, endlsn,'true');
select walminer_by_xid(xid,'true');或 select wal2sql(xid,'true');
'true'和‘false’代表是否为精确解析模式,reloid为目标表的oid(注意**不是**relfilenode)
select walminer_by_time(starttime, endtime,'false',reloid);或 select wal2sql(starttime, endtime,'true',reloid);
select walminer_by_lsn(startlsn, endlsn,'true',reloid);或 select wal2sql(startlsn, endlsn,'false',reloid);
select walminer_by_xid(xid,'true',reloid);或 select wal2sql(xid,'true',reloid);
create table t1_avatar(i int);
select walminer_table_avatar(avatar_table_name, missed_relfilenode);
select wal2sql();
实际操作
postgres=# select pg_current_wal_lsn(); pg_current_wal_lsn -------------------- 0/520D578(1 row)postgres=# insert into t1 values(1,1,'test_walminer');INSERT 0 1postgres=# insert into t1 values(2,1,'support_walminer');INSERT 0 1postgres=# select relfilenode from pg_class where relname ='t1';relfilenode-------------49275(1 row)
-- 这里执行了truncate导致t1表的relfilenode由49275变为49281
postgres=# truncate t1;TRUNCATE TABLEpostgres=# select relfilenode from pg_class where relname ='t1';relfilenode-------------49281(1 row)postgres=# insert into t1 values(3,1,'after truncate');INSERT 0 1postgres=# insert into t1 values(4,1,'after truncate');INSERT 0 1postgres=# select pg_current_wal_lsn();pg_current_wal_lsn--------------------0/52123B0(1 row)postgres=#-- 使用普通解析模式进行解析postgres=# select wal2sql('0/520D578'::pg_lsn,'0/52123B0'::pg_lsn);NOTICE: Add wal from current pg_wal directory, do not suggest use this way in produceNOTICE: Switch wal to 000000010000000000000005 on time 2020-06-18 11:16:06.383658+08
-- 这里会通知,49275在数据字典里没有找到
NOTICE: Con not find relfilenode 49275 in dictionary, ignored related recordswal2sql---------------------pg_minerwal success(1 row)postgres=# \xExpanded display is on.postgres=# select * from walminer_contents;-[ RECORD 1 ]-------------------------------------------------------------sqlno | 1xid | 268435484topxid | 0sqlkind | 1minerd | ttimestamp | 2020-06-18 11:14:45.380952+08op_text | INSERT INTO public.t1(i ,j ,k) VALUES(3 ,1 ,'after truncate')undo_text | DELETE FROM public.t1 WHERE i=3 AND j=1 AND k='after truncate'complete | t-[ RECORD 2 ]-------------------------------------------------------------sqlno | 1xid | 268435485topxid | 0sqlkind | 1minerd | ttimestamp | 2020-06-18 11:14:51.628774+08op_text | INSERT INTO public.t1(i ,j ,k) VALUES(4 ,1 ,'after truncate')undo_text | DELETE FROM public.t1 WHERE i=4 AND j=1 AND k='after truncate'complete | t
-- 解析结果中也没有前面两条数据,如果我们想要解析出丢失的数据,可以使用化身解析模式
postgres=#
-- 演示化身解析模式
postgres=# select walminer_stop();walminer_stop------------------walminer stoped!(1 row)-- 创建化身表postgres=# create table t1_avatar(i int,j int, k varchar);CREATE TABLE
-- 创建丢失的49275与化身表的映射
postgres=# select walminer_table_avatar('t1_avatar', 49275);walminer_table_avatar-----------------------------MAP[t1_avatar:49296]->49275(1 row)
-- 执行解析
postgres=# select wal2sql('0/520D578'::pg_lsn,'0/52123B0'::pg_lsn);NOTICE: Add wal from current pg_wal directory, do not suggest use this way in produceNOTICE: Switch wal to 000000010000000000000005 on time 2020-06-18 11:19:36.161709+08wal2sql---------------------pg_minerwal success(1 row)
-- 查询解析结果,可以看到丢失的数据以t1_avatar表的形式,重新体现出来了
postgres=# \xExpanded display is on.postgres=# select * from walminer_contents;-[ RECORD 1 ]----------------------------------------------------------------------sqlno | 1xid | 268435481topxid | 0sqlkind | 1minerd | ttimestamp | 2020-06-18 11:13:40.168623+08op_text | INSERT INTO public.t1_avatar(i ,j ,k) VALUES(1 ,1 ,'test_walminer')undo_text | DELETE FROM public.t1_avatar WHERE i=1 AND j=1 AND k='test_walminer'complete | t-[ RECORD 2 ]----------------------------------------------------------------------sqlno | 1xid | 268435482topxid | 0sqlkind | 1minerd | ttimestamp | 2020-06-18 11:13:48.141368+08op_text | INSERT INTO public.t1_avatar(i ,j ,k) VALUES(2 ,1 ,'support_walminer')undo_text | DELETE FROM public.t1_avatar WHERE i=2 AND j=1 AND k='support_walminer'complete | t-[ RECORD 3 ]----------------------------------------------------------------------sqlno | 1xid | 268435484topxid | 0sqlkind | 1minerd | ttimestamp | 2020-06-18 11:14:45.380952+08op_text | INSERT INTO public.t1(i ,j ,k) VALUES(3 ,1 ,'after truncate')undo_text | DELETE FROM public.t1 WHERE i=3 AND j=1 AND k='after truncate'complete | t-[ RECORD 4 ]----------------------------------------------------------------------sqlno | 1xid | 268435485topxid | 0sqlkind | 1minerd | ttimestamp | 2020-06-18 11:14:51.628774+08op_text | INSERT INTO public.t1(i ,j ,k) VALUES(4 ,1 ,'after truncate')undo_text | DELETE FROM public.t1 WHERE i=4 AND j=1 AND k='after truncate'complete | tpostgres=#
另外在化身解析模式中,不检查数据字典与wal日志是否匹配的问题,也就是说,拿到一个wal日志之后,如果你知道某一个relfilenode对应的表的表结构,那么就可以在不需要数据字典的情况下,把这儿wal日志拿到另外的数据库做化身解析,有兴趣的同学可以尝试一下...
化身解析一定要注意表结构的匹配问题,否则极有可能会产生意外的崩溃(暂时还没有找到规避崩溃的方法)
walminer 2.0 的限制
1. 当前walminer无法处理数据字典不一致问题,walminer始终以给定的数据字典为准,对于无法处理的relfilenode,那么会丢弃这一条wal记录(会有一个notice出现)
2. complete属性只有在wallevel大于minimal时有效
3. xid解析模式不支持子事务
4. 同时只能有一个walminer解析进程,否则会出现解析混乱
5. 化身表不支持toast,化身表与原表结构不一致时会产生崩溃
联系我
发现bug或者有好的建议可以通过邮箱(lchch1990@sina.cn)联系我
I Love PG
关于我们
中国开源软件推进联盟PostgreSQL分会(简称:中国PG分会)于2017年成立,由国内多家PostgreSQL生态企业所共同发起,业务上接受工信部中国电子信息产业发展研究院指导。中国PG分会是一个非盈利行业协会组织。我们致力于在中国构建PostgreSQL产业生态,推动PostgreSQL产学研用发展。
技术文章精彩回顾 PostgreSQL学习的九层宝塔 PostgreSQL职业发展与学习攻略 搞懂PostgreSQL数据库透明数据加密之加密算法介绍 一文读懂PostgreSQL-12分区表 一文搞懂PostgreSQL物化视图 PostgreSQL源码学习之:RegularLock Postgresql源码学习之词法和语法分析 2019,年度数据库舍 PostgreSQL 其谁? Postgres是最好的开源软件 PostgreSQL是世界上最好的数据库 从Oracle迁移到PostgreSQL的十大理由 从“非主流”到“潮流”,开源早已值得拥有 PG活动精彩回顾 创建PG全球生态!PostgresConf.CN2019大会盛大召开 首站起航!2019“让PG‘象’前行”上海站成功举行 走进蓉城丨2019“让PG‘象’前行”成都站成功举行 中国PG象牙塔计划发布,首批合作高校授牌仪式在天津举行 群英论道聚北京,共话PostgreSQL 相聚巴厘岛| PG Conf.Asia 2019 DAY0、DAY1简报 相知巴厘岛| PG Conf.Asia 2019 DAY2简报 独家|硅谷Postgres大会简报 PostgreSQL线上沙龙第一期精彩回顾 PostgreSQL线上沙龙第二期精彩回顾 PostgreSQL线上沙龙第三期精彩回顾 PostgreSQL线上沙龙第四期精彩回顾 PostgreSQL线上沙龙第五期精彩回顾 PostgreSQL线上沙龙第六期精彩回顾 直播回顾 | Bruce Momjian:原生分布式将在PG 14版本发布 PG培训认证精彩回顾 中国首批PGCA认证考试圆满结束,203位考生成功获得认证! 中国第二批PGCA认证考试圆满结束,115位考生喜获认证! 重要通知:三方共建,中国PostgreSQL认证权威升级! 近500人参与!首次PGCE中级、第三批次PGCA初级认证考试落幕! 2020年首批 | 中国PostgreSQL初级认证考试圆满结束 一分耕耘一分收获,第五批次PostgreSQL认证考试成绩公布





