写点什么

Oracle LogMiner 数据迁移实战

用户头像
yintianwen
关注
发布于: 2021 年 04 月 22 日
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 的同学,可以参考一下我整理的文档


  • Oralce Install (docker): https://github.com/TavenYin/database-cdc/blob/master/doc/oracle/oracle-install.md

  • Logminer:https://github.com/TavenYin/database-cdc/blob/master/doc/oracle/oracle12c-logminer.md

小试牛刀

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


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

1. 构建数据字典

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


# 这是一条常规的SQLINSERT 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;该操作会占用一定数据库资源

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


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


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

2. 添加日志文件

# 查询目前的redol ogSQL> 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 logSQL> 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


实战

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


由于篇幅问题,这里我只讨论思路和我的一些想法。


完整代码参考👉 https://github.com/TavenYin/database-cdc/tree/master/oracle-logminer

1. 整体思路

相关实现思路参考自 Debezium



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


  • Redo Log 切换后,会生成新的归档,我们需要 Add 新的归档日志

  • 长时间开启 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。这么短时间内被覆盖的情况几乎不可能发生。



2. 处理 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",所以这个策略不适合我们的程序。



3. 迁移进程宕机处理

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


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


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


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



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


4. SQL 解析

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


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

DEMO

运行效果如下



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

参考

  • Oracle Redo : https://docs.oracle.com/cd/B28359_01/server.111/b28310/onlineredo001.htm

  • Oracle Archived : https://docs.oracle.com/cd/B28359_01/server.111/b28310/archredo001.htm

  • Oracle Flashback : https://docs.oracle.com/cd/E11882_01/appdev.112/e41502/adfns_flashback.htm

  • LogMiner : https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sutil/oracle-logminer-utility.html

  • Debezium Oracle Connector : https://debezium.io/documentation/reference/connectors/oracle.html

发布于: 2021 年 04 月 22 日阅读数: 34
用户头像

yintianwen

关注

还未添加个人签名 2019.08.14 加入

还未添加个人简介

评论

发布
暂无评论
Oracle LogMiner 数据迁移实战