暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Walminer2.0 Beta功能改进说明

作者

李传成:中国PostgreSQL分会认证专家

             瀚高软件工程师


 前言


walminer是一款PostgreSQL的wal日志的解析工具,它可以依据数据字典解析出产生wal日志中隐含的DML语句。第一个版本的xlogminer工具需要wal为logical级别,而且还有表IDENTITY级别的要求。第二个版本的walminer在易用性上不太友好,无法做到精准解析。walminer2.0是第三个版本,这个版本在上一个版本的基础上增强了易用性,可以做到精准解析,并且做了大量的代码重构以期获得更好的扩展性和对PG13的支持。

项目开源地址为:

https://gitee.com/movead/XLogMiner

有兴趣的同学可以下载使用, 目前walminer2.0还处于开发过程,如果大家有好的改进意见可以提issue,顺便记得star~~~


 功能增强


1. 支持指定LSN范围解析
2. 支持特定事务ID解析
3. 支持精确解析语法
4. 增加快捷解析和化身解析模式

 版本支持


支持PG9.5,PG9.6,PG10,PG11,PG12,PG13,与上一个版本不同,walminer2.0的代码完成了合并,使用同一份walminer代码可以对应各个版本的PG的编译安装。

 编译安装


编译一:从PG源码编译
将walminer目录放置到编译通过的PG工程的"../contrib/"目录下
进入walminer目录,执行命令
    make && make install
    编译二:依据安装PG的编译
    配置pg的bin路径至环境变量
      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工具使用的标准步骤


        walminer2.0与之前的版本一样,都支持在产生wal日志的数据库(生产库)或者在另外的数据库(测试库)执行walminer解析操作。所不同的是,在测试库解析过程需要首先在生产库生成数据字典,然后拿到测试库执行导入数据字典的操作。本博客主要讲述walminer2.0更新的使用接口,用生产库直接解析的方式进行说明,需要在测试库解析的同学可以去阅读下说明文档。
        1. 编译安装
        略...(不会的可以阅读说明文档或者邮件咨询我)
        2. 创建extension

          postgres=# create extension walminer;
          CREATE EXTENSION
          postgres=#

          3. 清理walminer内存空间

            postgres=# select walminer_stop();
            walminer_stop
            ------------------
            walminer stoped!
            (1 row)
            postgres=#
            为什么清理空间放在了最前面呢,如果在同一个session中执行多次解析,且有一次执行失败,有些数据可能会遗留在内存中,影响下一次的解析。因此如果你某一次执行解析失败,最好执行一下这个接口,清理一下遗留的内存数据。
            4. 管理要解析的wal日志

              添加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)
              目前仅支持一个文件一个文件的移除。
              5. 执行解析

                postgres=# select wal2sql();
                NOTICE: Switch wal to 000000010000000000000074 on time 2020-06-17 21:29:08.289991+08
                walminer_all
                ---------------------
                pg_minerwal success
                (1 row)
                postgres=#

                walminer2.0简化了解析函数的入参,同时也修改了解析函数的函数名。这里wal2sql()只是最简单的解析方式,还有更多的解析模式将会在下面给出。
                6. 查询解析结果

                  postgres=# select * from walminer_contents;
                  -- 这里不显示结果了
                  postgres=# \d walminer_contents
                  Table "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, --事务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所在的事务是不完整解析的
                  )*/


                   walminer 2.0 的解析模式


                  知识储备
                  因为在下面的说明中会提到relfilenode,这里补充一点关于表的oid和relfilenode的说明:

                    postgres=# select oid, relfilenode,relname from pg_class where relname ='t2';
                    oid | relfilenode | relname
                    -------+-------------+---------
                    90506 | 90506 | t2
                    (1 row)
                    postgres=# truncate t2;
                    TRUNCATE TABLE
                    postgres=# select oid, relfilenode,relname from pg_class where relname ='t2';
                    oid | relfilenode | relname
                    -------+-------------+---------
                    90506 | 98623 | t2
                    (1 row)
                    postgres=#
                    oid是表的唯一标识,relfilenode是表数据存放在硬盘的文件名的标识,在执行vacuum full;truncate等操作时,会导致表在硬盘上存储的文件名的改变,因此relfilenode也会改变。上面这个例子中,新建t2表时t2的oid和relfilenode是相同的,经过一次truncate后,relfilenode发生了改变。好了下面进行walminer2.0的功能讲述。
                    普通解析
                    walminer2.0支持如下解析方式:
                    -- 全部解析

                      select walminer_all(); 
                      select wal2sql();
                      -- 时间范围解析

                        select walminer_by_time(starttime, endtime); 
                        select wal2sql(starttime, endtime);
                        -- lsn范围解析

                          select walminer_by_lsn(startlsn, endlsn); 
                          select wal2sql(startlsn, endlsn);
                          -- xid解析(注:只支持指定xid,不支持xid范围,目前不支持子事务。)

                            select walminer_by_xid(xid); 
                            select wal2sql(xid);
                            实际操作

                              postgres=# create table t1(i int, j int, k varchar);
                              CREATE TABLE
                              postgres=# 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 1
                              postgres=# insert into t1 values(2,1,'support_walminer2.0');
                              INSERT 0 1
                              postgres=# 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+08
                              walminer_by_time
                              ---------------------
                              pg_minerwal success
                              (1 row)


                              postgres=# \x
                              Expanded display is on.
                              postgres=# select * from walminer_contents;
                              -- 这里我们不显示结果了
                              postgres=#

                              快捷解析
                              加载wal日志步骤可以省略,默认直接加载当前wal路径下的所有wal文件。这个解析模式只在学习本工具时使用,在生产数据库中,可能会因为wal段切换而导致解析失败。
                              依旧使用上面的数据做解析测试:

                                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 produce
                                NOTICE: Switch wal to 000000010000000000000001 on time 2020-06-17 22:53:52.470086+08
                                wal2sql
                                ---------------------
                                pg_minerwal success
                                (1 row)
                                postgres=# select * from walminer_contents;
                                -- 这里我们不显示结果了

                                精确解析
                                walminer的解析理论基础是在checkpoint之后对每一个数据页的第一次有意义的修改都要进行FPW,因此理论上只要找到了checkpoint点的开始点,那么walminer可以解析开始点(如下图的lsn1点)之后的所有记录(当然是在数据字典匹配的情况下)。如果用户想完全解析lsn2到lsn3之间的记录,可以使用精确解析模式,walminer会在加载的wal日志中查找lsn2之前的lsn1点,如果找到lsn1点那么此次精确解析可以完成,如果找不到lsn1点会报错停止解析,并要求用户添加更多wal日志。
                                精确解析模式支持时间范围解析、lsn范围解析和xid解析,如下解析语法,在普通解析模式下增加一个‘true’参数:
                                -- 时间范围解析
                                  select walminer_by_time(starttime, endtime,'true'); 
                                  select wal2sql(starttime, endtime,'true');
                                  -- lsn范围解析
                                    select walminer_by_lsn(startlsn, endlsn,'true'); 
                                    select wal2sql(startlsn, endlsn,'true');
                                    -- xid解析
                                      select walminer_by_xid(xid,'true'); 
                                      select wal2sql(xid,'true');
                                      操作也很简单,这里也不再演示。
                                      单表解析
                                      只对指定表的解析,如下语法:
                                        'true'和‘false’代表是否为精确解析模式,reloid为目标表的oid(注意**不是**relfilenode)
                                        --在add的wal日志中查找对应时间范围的wal记录
                                          select walminer_by_time(starttime, endtime,'false',reloid); 
                                          select wal2sql(starttime, endtime,'true',reloid);
                                          --在add的wal日志中查找对应lsn范围的wal记录
                                            select walminer_by_lsn(startlsn, endlsn,'true',reloid); 
                                            select wal2sql(startlsn, endlsn,'false',reloid);
                                            --在add的wal日志中查找对应xid的wal记录
                                              select walminer_by_xid(xid,'true',reloid);
                                              select wal2sql(xid,'true',reloid);
                                              操作也很简单,这里也不再演示。
                                              化身解析
                                              如果一个表被drop或者被truncate等操作,导致新产生的数据字典不包含旧的数据库中所包含的relfilenode,那么使用新的数据字典无法解析出旧的wal日志中包含的某些内容。在知晓旧表的表结构的前提下,可以使用化身解析模式。
                                              -- 假设表t1被执行了vacuum full,执行vacuum full前的relfilenode为16384
                                              -- 新建表t1的化身表
                                                create table t1_avatar(i int);
                                                -- 执行化身映射
                                                  select walminer_table_avatar(avatar_table_name, missed_relfilenode);
                                                  -- 执行解析
                                                    select wal2sql();
                                                    -- 查看解析结果时,会发现,对t1表的数据都以t1_avatar表的形式展现在输出结果中

                                                     实际操作


                                                    -- 测试数据准备
                                                      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 1
                                                      postgres=# insert into t1 values(2,1,'support_walminer');
                                                      INSERT 0 1
                                                      postgres=# select relfilenode from pg_class where relname ='t1';
                                                      relfilenode
                                                      -------------
                                                      49275
                                                      (1 row)

                                                      -- 这里执行了truncate导致t1表的relfilenode由49275变为49281

                                                        postgres=# truncate t1;
                                                        TRUNCATE TABLE
                                                        postgres=# select relfilenode from pg_class where relname ='t1';
                                                        relfilenode
                                                        -------------
                                                        49281
                                                        (1 row)


                                                        postgres=# insert into t1 values(3,1,'after truncate');
                                                        INSERT 0 1
                                                        postgres=# insert into t1 values(4,1,'after truncate');
                                                        INSERT 0 1
                                                        postgres=# 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 produce
                                                        NOTICE: 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 records
                                                          wal2sql
                                                          ---------------------
                                                          pg_minerwal success
                                                          (1 row)
                                                          postgres=# \x
                                                          Expanded display is on.
                                                          postgres=# select * from walminer_contents;
                                                          -[ RECORD 1 ]-------------------------------------------------------------
                                                          sqlno | 1
                                                          xid | 268435484
                                                          topxid | 0
                                                          sqlkind | 1
                                                          minerd | t
                                                          timestamp | 2020-06-18 11:14:45.380952+08
                                                          op_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 | 1
                                                          xid | 268435485
                                                          topxid | 0
                                                          sqlkind | 1
                                                          minerd | t
                                                          timestamp | 2020-06-18 11:14:51.628774+08
                                                          op_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 produce
                                                                  NOTICE: Switch wal to 000000010000000000000005 on time 2020-06-18 11:19:36.161709+08
                                                                  wal2sql
                                                                  ---------------------
                                                                  pg_minerwal success
                                                                  (1 row)

                                                                  -- 查询解析结果,可以看到丢失的数据以t1_avatar表的形式,重新体现出来了

                                                                    postgres=# \x
                                                                    Expanded display is on.
                                                                    postgres=# select * from walminer_contents;
                                                                    -[ RECORD 1 ]----------------------------------------------------------------------
                                                                    sqlno | 1
                                                                    xid | 268435481
                                                                    topxid | 0
                                                                    sqlkind | 1
                                                                    minerd | t
                                                                    timestamp | 2020-06-18 11:13:40.168623+08
                                                                    op_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 | 1
                                                                    xid | 268435482
                                                                    topxid | 0
                                                                    sqlkind | 1
                                                                    minerd | t
                                                                    timestamp | 2020-06-18 11:13:48.141368+08
                                                                    op_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 | 1
                                                                    xid | 268435484
                                                                    topxid | 0
                                                                    sqlkind | 1
                                                                    minerd | t
                                                                    timestamp | 2020-06-18 11:14:45.380952+08
                                                                    op_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 | 1
                                                                    xid | 268435485
                                                                    topxid | 0
                                                                    sqlkind | 1
                                                                    minerd | t
                                                                    timestamp | 2020-06-18 11:14:51.628774+08
                                                                    op_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=#

                                                                    另外在化身解析模式中,不检查数据字典与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产学研用发展。



                                                                    欢迎投稿

                                                                    做你的舞台,show出自己的才华 。

                                                                    投稿邮箱:partner@postgresqlchina.com

                                                                                                   

                                                                                                     ——愿能安放你不羁的灵魂


                                                                    技术文章精彩回顾




                                                                    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认证考试成绩公布

                                                                    文章转载自开源软件联盟PostgreSQL分会,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                                    评论