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

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

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

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

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

这篇文章把这件事聊透。

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

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

以一条UPDATE为例:

sql 复制代码
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_REDOSQL_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主流版本的通用机制,不同版本可能有细微差异。实际解析时需要根据目标版本做适配。

相关推荐
IT邦德1 小时前
BIC-QA如何重新定义数据库知识服务
运维·数据库
逐米时代1 小时前
BOM智能构建:全链路一致性自动校验
大数据·数据库·人工智能
starzy19901 小时前
Flink Session Window 详解及代码实现:从用户会话切割到窗口合并机制
java·flink
SelectDB技术团队2 小时前
StarRocks 迁移至 Apache Doris 完整指南:三步完成结构、数据与业务平滑切换
数据库·人工智能·sql·clickhouse·apache doris·selectdb·湖仓架构升级
Gauss松鼠会2 小时前
GaussDB 系统表与系统视图详解(内网运维版)
运维·服务器·网络·数据库·gaussdb·经验总结
倔强的石头_2 小时前
OceanBaseVS金仓:同一条复杂 SQL,为什么架构选择会改变延迟曲线
数据库
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue图书借阅管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
李金虎_2 小时前
大模型搜索时代的 GEO 工程实践:从零搭建仿真环境到可控变量的完整复盘
数据库·机器学习
YangYang9YangYan2 小时前
商业分析师校招拆解|2026 秋招 Python 要求、工具栈与面试考点
数据库·人工智能·数据分析