TLA技术头像
关注

LogMiner vs 裸日志解析(三):Oracle日志解析中的“前镜像”和“后镜像”,到底怎么用?

聊Oracle日志解析,绕不开两个词:前镜像(Before Image) 和后镜像(After Image) 。

第一次听到这两个词的时候,很多人会以为是什么高深的概念。其实说白了很简单:

  • 前镜像:数据修改之前长什么样
  • 后镜像:数据修改之后长什么样

但问题在于——这两个东西在redo log里到底是怎么存的?CDC解析的时候到底该用哪个?

这篇文章把这件事聊透。

前镜像和后镜像,到底存在哪?

redo log里,每个Change Vector的Body部分,存的就是变更的具体数据。

以一条UPDATE为例:

UPDATE employees SET salary = 8000 WHERE employee_id = 1001;

假设修改前salary是5000。这个Change Vector的Body里,会记录:

  • 前镜像:employee_id=1001这一行的原始数据(salary=5000,以及其他列的值)
  • 后镜像:修改后的数据(salary=8000,以及其他列的值)

但这里有一个很容易被忽略的细节:前镜像和后镜像不一定都完整。

Oracle的redo log里,前镜像的完整性取决于补充日志(Supplemental Logging) 的配置。

补充日志:前镜像完整性的开关

Oracle默认不记录完整的前镜像。它只记录修改涉及的列的前后值。

比如你执行UPDATE employees SET salary = 8000 WHERE employee_id = 1001,默认情况下:

  • 前镜像:只记录了employee_id和salary的旧值
  • 后镜像:只记录了employee_id和salary的新值

其他列(比如name、department、hire_date)不记录。

这意味着什么?

如果你只靠redo log来还原数据变更,你拿到的是一个“残缺”的前镜像——只知道哪一行的哪个字段被改了,但不知道这一行的完整数据长什么样。

要拿到完整的前镜像,必须开启补充日志。

补充日志有几个级别:

  • 最小补充日志(Minimal) :默认级别,只记录必要的信息
  • 主键补充日志(Primary Key) :额外记录主键列的值
  • 唯一键补充日志(Unique Key) :额外记录唯一键列的值
  • 全列补充日志(All Columns) :记录所有列的前镜像

ALL_COLUMNS级别的补充日志,会让redo log里记录每一行的完整前镜像。但这会显著增加redo log的大小——可能增加20%-50%的日志量。

这就是为什么CDC方案通常要求开启补充日志。 LogMiner需要前镜像来重构完整的行数据,Debezium和Flink CDC的文档里也都明确要求开启ALL_COLUMNS补充日志。

前镜像和后镜像,CDC到底用哪个?

答案是:两个都用,但用途不同。

后镜像用于同步。

CDC的核心目标是“把源库的变更同步到目标端”。后镜像是修改之后的值——目标端需要的就是这个值。你拿到后镜像,就知道目标端应该把这一行改成什么样。

前镜像用于判断和回滚。

前镜像虽然不直接同步到目标端,但在解析过程中有非常重要的作用:

第一,判断事务是否回滚。

如果一个事务最终回滚了,redo里会有OP 5.1(撤销修改)的Change Vector。这个Change Vector的Body里存的就是前镜像——Oracle要把数据恢复到修改之前的状态。解析器看到OP 5.1,就知道这个事务要回滚,所有变更都不应该输出。

第二,处理UPDATE的WHERE条件。

LogMiner在重构SQL的时候,需要用前镜像来构造WHERE条件。比如UPDATE employees SET salary = 8000 WHERE employee_id = 1001,LogMiner需要从Change Vector里提取employee_id的旧值来拼出WHERE子句。

第三,处理链式变更。

如果同一行数据在一个事务里被修改了多次(UPDATE→UPDATE→UPDATE),前镜像和后镜像会形成一条链。解析器需要按顺序应用这些变更,才能得到最终结果。

第四,处理行迁移和行链接。

如果一行数据太大,跨了多个数据块,前镜像可以帮助解析器识别和还原完整的行数据。

实际解析中,前镜像和后镜像会遇到哪些坑?

第一个坑:前镜像不完整。

如果没有开启补充日志,前镜像只包含修改涉及的列,其他列是空的。这时候你拿到的UPDATE变更,只知道salary从5000改成了8000,但不知道这一行的其他字段是什么。

对于CDC来说,这通常不是问题——你只需要同步变更的字段。但如果目标端需要完整的行数据(比如做全量比对),前镜像不完整就会导致数据缺失。

第二个坑:大字段的前镜像被截断。

对于BLOB、CLOB这些大字段,前镜像和后镜像可能不是完整的——redo log里可能只记录了locator的变更,实际数据在别的数据块里。解析器需要跟踪这些数据块的变更,把完整的数据拼出来。

第三个坑:LONG类型的前镜像不记录。

Oracle的LONG类型在redo log里不记录前镜像——这是一个历史遗留问题。LogMiner不支持LONG类型,很大程度上就是因为前镜像不完整。

第四个坑:虚拟列没有前镜像。

虚拟列的值是计算出来的,redo log里不记录虚拟列本身的前后镜像。解析器需要拿到依赖列的值,然后按照虚拟列的定义自己计算。

第五个坑:行迁移导致的前镜像跨块。

如果一行数据被迁移到了新的数据块,前镜像和后镜像可能分布在不同的块里。解析器需要能跨块关联,把完整的数据拼出来。

LogMiner怎么处理前镜像和后镜像?

LogMiner在处理前镜像和后镜像的时候,有一个关键的限制:它需要连接数据库,依赖数据字典。

具体来说,LogMiner拿到Change Vector之后,需要:

  1. 从Change Vector里提取前镜像和后镜像
  2. 根据对象ID去数据字典里查这个表的结构——有哪些列、什么类型、什么顺序
  3. 按列的顺序和类型,把前镜像和后镜像解码成可读的值
  4. 拼成SQL语句(SQL_REDO和SQL_UNDO)

这个过程有几个问题:

第一,必须连接数据库。 没有数据字典,LogMiner只能返回内部对象ID和十六进制数据,根本没法用。

第二,字典必须是最新的。 如果表结构变了(加了列、改了类型),LogMiner的字典必须同步更新,否则解析出来的数据就是错的。

第三,某些类型处理不了。 前面说的BLOB、CLOB、XMLTYPE这些,LogMiner拿到前镜像和后镜像之后,不知道怎么解码,直接标记为NULL或UNSUPPORTED。

裸日志解析怎么处理前镜像和后镜像?

直接解析二进制的时候,前镜像和后镜像的处理逻辑是自己控制的。

核心思路:不依赖数据字典,按二进制格式直接解码。

具体来说:

第一,识别Change Vector的类型。 通过OP Code判断这是INSERT、UPDATE还是DELETE。INSERT没有前镜像,DELETE没有后镜像,UPDATE两个都有。

第二,解析Body里的二进制数据。 Change Vector的Body里,前镜像和后镜像按列顺序排列。每一列的编码方式由数据类型决定——NUMBER有NUMBER的编码,VARCHAR2有VARCHAR2的编码。

第三,自己维护类型映射。 不依赖Oracle的数据字典,自己维护表和列的元数据。列的顺序、类型、长度,全部自己管理。这样就不会受LogMiner字典翻译层的限制。

第四,按类型逐列解码。 NUMBER类型按Oracle的NUMBER编码规则解码,VARCHAR2按字符集解码,DATE按7字节格式解码,BLOB/CLOB按locator关联实际数据块解码。

第五,处理回滚判断。 读到OP 5.1(撤销修改)时,把对应的前镜像应用到已经解析的变更上,把数据恢复到修改前的状态。如果整个事务最终回滚,所有变更都不输出。

我们团队在做TLA的时候,前镜像和后镜像的解析就是按这个思路实现的。自己维护列的类型和顺序,自己按二进制格式解码,不依赖LogMiner的字典翻译层。所以BLOB、CLOB、XMLTYPE这些LogMiner不支持的类型,我们都能处理。流式解析,不缓存整个事务,大事务也不会OOM。

一个容易搞混的点

前镜像不是“上一行的值”,后镜像不是“下一行的值”。

前镜像和后镜像是针对同一个数据块、同一行数据的修改前后状态。不是两个不同行的值。

前镜像不一定完整。 取决于补充日志的配置。默认情况下只记录修改涉及的列。

后镜像也不一定完整。 对于大字段,后镜像可能只是locator,不是完整数据。

前镜像和后镜像可能不在同一个Change Vector里。 一个大的UPDATE可能被拆成多个Change Vector,前镜像和后镜像分散在不同的record里。

说句实在话

前镜像和后镜像,名字听起来很学术,其实本质很简单——就是修改前和修改后的数据。

但在实际解析中,这两个东西涉及的问题很多:补充日志的配置、数据类型的处理、大字段的关联、回滚的判断、行迁移的处理……

套壳LogMiner的方案,把这些事情交给了LogMiner。代价是:需要连接数据库、依赖数据字典、不支持某些类型、大事务OOM。

裸日志解析的方案,自己处理前镜像和后镜像的解析。难走,但走通了就不受任何人限制。

这是“LogMiner vs 裸日志解析”系列的第三篇。后面会继续聊其他几个坑——大事务OOM、SCN追不上、在线日志读写冲突,等等。

欢迎交流。


补充:文中提到的补充日志配置和数据类型处理方式基于Oracle主流版本的通用机制,不同版本可能有细微差异。实际解析时需要根据目标版本做适配。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/m0_60522596/article/details/166088796

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--