Oracle LogMiner 数据迁移实战

LogMiner 是什么

LogMiner 是Oracle官方提供的工具,可以解析 Redo log 和 Archived Redo log

LogMiner 可以做什么?

官方文档中列举了很多,大家可以自己去看下。

我们目前的项目在使用基于LogMiner 的 Debezium Oracle Connector 做数据迁移

Oracle LogMiner 数据迁移的原理是什么?

首先需要了解几个概念,这里简单介绍下

  • Redo log:Redo中记录了所有对数据块的更改,Oralce 要求至少有两个以上的Redo Log Group

  • Archived Redo log:当一个Redo Log 写满之后,会发生日志切换,数据的更改会记录到下一个Redo Log中(所以一定要有两个以上的Redo)。如果开启了归档模式,Oracle 会将写满的Redo Log 归档。

  • SCN (System Change Number):Oracle 内部逻辑时间戳

  • Flashback:通过闪回查询 SELECT ... AS OF SCN 可以查询Oracle某个时间点的全量数据


思路如下:

  1. 首先查询出一下当前的SCN
  2. 根据SCN 查询出这一时刻的全量数据
  3. 通过Logminer 指定Start_SCN,获取增量数据

安装与配置

想尝试却不太熟悉Oracle的同学,可以参考一下我整理的文档

小试牛刀

在准备好了环境之后,我们来开箱体验一下Logminer

logminer 用户登录 conn c##logminer/password

  1. 构建数据字典

    LogMiner使用数据字典将内部对象标识符和数据类型转换为正常字段和数据格式

      # 这是一条常规的SQL
       INSERT INTO HR.JOBS(JOB_ID, JOB_TITLE, MIN_SALARY, MAX_SALARY)  VALUES('IT_WT','Technical Writer', 4000, 11000);
      # 如果没有数据字典,数据会是这样的
      insert into "UNKNOWN"."OBJ# 45522"("COL 1","COL 2","COL 3","COL 4")     
      values (HEXTORAW('45465f4748'),HEXTORAW('546563686e6963616c20577269746572'),HEXTORAW('c229'),HEXTORAW('c3020b'));
    

    官方文档中提到三种方式:

    • 在线数据字典:当你可以访问创建Redo的源数据库并且表结构不会发生任何变动时。可以考虑使用在线数据字典。这是最简单有效的,也是Oracle的推荐选项
      由于在线数据字典永远存储的是最新的结构。如果发生了表结构变动,Logminer 捕获到旧版本的数据,SQL将会如上述代码块中那样

    • 提取数据字典到redo中:
      需要执行命令BEGIN DBMS_LOGMNR_D.BUILD (options => DBMS_LOGMNR_D.STORE_IN_REDO_LOGS); END;
      该操作会占用一定数据库资源

      LogMiner 在启动时会通过指定的数据字典选项维护一个内部数据字典,当启动LogMiner时指定 DBMS_LOGMNR.DDL_DICT_TRACKING,LogMiner会自动捕获DDL来更新内部字典,这样即使发生了表结构变动时,也可以正确的解析DDL。注意:该选项不能和在线数据字典同时使用
      更多解释参考Oracle文档:https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sutil/oracle-logminer-utility.html#GUID-56743517-A0C0-4CCD-9D20-2883AFB5683B

    • 提取数据字典到Flat File:Oracle维护该选项是为了兼容历史版本,本文并没有使用到该方式,不多做介绍

    这一步我选择在线数据字典,什么都不用做,直接进入下一步

  2. 添加日志文件

        # 查询目前的redol og
    SQL> select member from v$logfile;
    
    MEMBER
    --------------------------------------------------------------------------------
    /opt/oracle/oradata/ORCLCDB/redo03.log
    /opt/oracle/oradata/ORCLCDB/redo02.log
    /opt/oracle/oradata/ORCLCDB/redo01.log
    
         # 添加redo log
    SQL> EXECUTE DBMS_LOGMNR.ADD_LOGFILE( -
       LOGFILENAME => '/opt/oracle/oradata/ORCLCDB/redo03.log', -
       OPTIONS => DBMS_LOGMNR.NEW);
    EXECUTE DBMS_LOGMNR.ADD_LOGFILE( -
       LOGFILENAME => '/opt/oracle/oradata/ORCLCDB/redo02.log', -
       OPTIONS => DBMS_LOGMNR.ADDFILE);
    EXECUTE DBMS_LOGMNR.ADD_LOGFILE( -
       LOGFILENAME => '/opt/oracle/oradata/ORCLCDB/redo01.log', -
       OPTIONS => DBMS_LOGMNR.ADDFILE);
    
  3. START_LOGMNR

    # 使用在线数据字典进行log解析
    SQL> EXECUTE DBMS_LOGMNR.START_LOGMNR(-
    OPTIONS => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG);
    

    然后执行一条INSERT

  4. 查询结果
    通过查询V$LOGMNR_CONTENTS 获取LogMiner捕获的结果。当执行该视图查询时,LogMiner会按照顺序解析Redo和Archived Log,所有执行时间会有一点慢

      SELECT OPERATION, SQL_REDO, SQL_UNDO
      FROM V$LOGMNR_CONTENTS
      WHERE table_name='TEST_TAB';
    

    结果如下,可以看到我们刚刚INSERT的SQL

    OPERATION                   SQL_REDO                                SQL_UNDO
    
    INSERT              insert into "SCOTT"."TEST_TAB"                  delete from "SCOTT"."TEST_TAB"              
                        ("ID","PRICE","CURRENT_DATE")                   where "ID" = 'BEC21E77FD10311AE053020011ACC051'
                        values                                          and "PRICE" = '22' 
                        ('BEC21E77FD10311AE053020011ACC051','22',NULL); and "CURRENT_DATE" IS NULL and ROWID = 'AAAWbEAAKAAAACHAAC';
    

实战

我们已经知道了迁移的思路和Logminer如何使用,现在可以动手搞一个demo了。

由于篇幅问题,这里我只讨论思路和我的一些想法。完整代码参考👉 https://github.com/TavenYin/database-cdc/tree/master/oracle-logminer

  1. 整体思路
    相关实现思路参考自Debezium
image.png

需要解释一下第四步为什么,发生Redo发生切换时,需要重启Logminer流程,两点原因

  1. Redo Log 切换后,会生成新的归档,我们需要Add新的归档日志
  2. 长时间开启LogMiner会话,会导致PGA使用量一直上升无法释放,End LogMiner 可以解决这个问题。所以代码逻辑中需要找一个时机去重启LogMiner,而Redo 切换这个时间点确实也挺合适的。

写到这的时候,我突然有了一个疑问

我们刚刚已经说过了,只有在查询 V$LOGMNR_CONTENTS 时,LogMiner才会去解析Redo Log,然后动态的生成视图。

参考上图。如果在第四步和第六步之间,程序检查到没有RedoLog切换准备继续执行。突然插入了大量数据导致Current Redo Log 被覆盖(注意必须是已经被覆盖而不是切换)了,此时是不是我们再查询 V$LOGMNR_CONTENTS 岂不是会丢失一部分数据?

由于start_logminer时会指定,起始和结束SCN,所以即使下次执行时添加了新的Archived Log,由于SCN已经被跨过去了,所以一定不会读这部分数据

在我做了测试之后发现,如果情况真的如此极端,确实会这样。

那么Debezium为什么没有考虑这个问题呢?

个人理解,在生产环境通常Redo Log 不会频繁切换,并且一定会有多个Redo Group。这么短时间内被覆盖的情况几乎不可能发生。


  1. 处理 V$LOGMNR_CONTENTS 结果集
    最开始在看Debezium源码的时候,没仔细注意这个地方,在自己动手搞一遍之后,发现这个地方的逻辑有点麻烦

    V$LOGMNR_CONTENTS 每一行可能是事务的提交、回滚,DDL,DML

    上面提到了一个 TransactionalBuffer是什么?

    我们在读取 V$LOGMNR_CONTENTS 会发生如下图的情况,因为每次只从startScn 读取到 当前Scn。而这中间可能发生的情况是,事务并没有Commit,但是我们拿到了其中一部分的DML,我们并不能确定这些DML是不是要Commit,所以需要将这些“一半”的事务暂时缓存在内存中

其实在调用 DBMS_LOGMNR.START_LOGMNR 时,可以指定一个选项 COMMITTED_DATA_ONLY,仅读出已提交的事务。这样就不必要这么麻烦的处理结果集了。但是为什么不选择 COMMITTED_DATA_ONLY?使用该策略会一直等待事务提交才会响应客户端,这很容易造成 "Out of Memory",所以这个策略不适合我们的程序。


  1. 迁移进程宕机处理

    数据迁移必定是一个漫长的过程,如果在执行中遇到什么意外,导致Java进程挂了,那么一切都要从头开始吗?

    如果我们能确定某个SCN之前的所有记录都已经被处理了,那么下次重启时从这个SCN开始处理即可

    两处可以确定之前SCN已经被全部处理的地方,代码如下:

    a. 当前TransactionalBuffer中没有数据,代表END_SCN之前所有的事务都已经被提交了

    b. 提交事务时,如果当前要提交的事务的Start_SCN 早于TransactionalBuffer中的所有事务

  1. SQL解析
    如果你想将Oracle的数据同步到其他数据库(包含NoSQL)的话,最好的办法是将SQL解析成结构化的对象,让下游服务去消费这些对象。

    Debezium的做法,我还没抽出空研究。目前的解决方法是用com.alibaba.druid.sql.SQLUtils,这个类可以将SQL解析成结构化对象,我们再对这些对象进行一些处理,即可让下游服务消费了。

DEMO

运行效果如下

DEMO运行效果

GitHub 👉 https://github.com/TavenYin/database-cdc/tree/master/oracle-logminer

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容