ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LogMiner vs 裸日志解析(十五):给表中间加一列,为什么能把同步链路搞挂?

LogMiner vs 裸日志解析(十五):给表中间加一列,为什么能把同步链路搞挂?

LogMiner vs 裸日志解析(十五):给表中间加一列,为什么能把同步链路搞挂?

系列第 15 篇。前一篇聊了"接入前要先改生产库的配置",这一篇聊一件更日常的事:schema 一变,链路就可能出问题。


一句开发最常说的话

"我就加个字段,很快的。"

做数据同步的人,最怕听到这句话。因为对不少 CDC 方案来说,表结构一动,链路就可能悄悄坏掉——不是报错停摆那种坏,是"还跑着,但数据已经不对了"。


01 真实场景:加了一列,下游整张表错位

朋友公司一个订单同步任务,跑了大半年没出过事。

某天开发要给ORDERS表加个CHANNEL字段,顺手加在了表中间(他想让新字段挨着STATUS,看着顺眼)。

任务没报错,也没停。第二天对账,数据却对不上——下游ORDERS表里,AMOUNT里装着原来的STATUS,STATUS里装着原来的CREATE_TIME……从新列往后,整排错位。

他查了一整天,最后发现根因就俩字:加列。


02 底层原理:LogMiner 靠的是"当下的数据字典"

要理解为什么会错位,得先知道 LogMiner 是怎么把日志翻译成人话的。

redo 日志里记的不是"表名、列名",而是对象号 + 列号(column position)——物理层面的编号。LogMiner 要输出你能看懂的INSERT INTO ORDERS (ORDER_ID, ...) VALUES (...),就得拿一份"号码 → 名字"的对照表去翻译。

问题就在这份对照表:它来自数据库当前的数据字典,不是日志产生那一刻的快照。

列一旦变动,编号和名字的对应关系就变了:

  • 中间插列→ 插入点之后的列号全部后移,旧日志按新字典翻译 →整排错位(最典型);
  • 改列类型→ 解析出来的值和类型对不上;
  • 删列 / 重排列→ 同样对不上;
  • DROP TABLE / TRUNCATE→ 下游要么收到一堆无处安放的变更,要么该清空的没清空。

LogMiner 的 DDL 跟踪本身也不完整:它没法把每一种 DDL 都准确翻译成下游能消费的事件,新建的物化视图也不会自动回填。


03 社区里的同款

这不是孤例。翻一翻 Debezium 和 Flink CDC 的文档与 issue,关于 schema 变化的限制写得都很克制,但很实在:

  • Debezium 对 Oracle 给了一份明确的DDL 支持列表,没在列表里的 DDL,行为是不保证的;
  • Flink CDC 的 Oracle 连接器长期被反馈DDL 同步不完整、Schema Evolution 能力有限;
  • 常见的下游症状:Kafka Connect 报反序列化失败、目标表结构对不上、任务反复重启。

一句话:在 Oracle 上做 schema 同步,是件很脆的事。


04 麻烦在哪

  • "加个字段"是最日常的开发动作,却可能让链路静默损坏——可怕的不是崩溃,是"看起来没事"。
  • 错位往往是事后发现的:等对账、报表口径不对了才回头查,已经积压了一堆脏数据。
  • 修起来更麻烦:要停任务、清目标、回溯重放,还得跟业务对账,一次能折腾好几天。
  • 历史日志救不了:就算你有半年前的归档日志,拿"现在的字典"去挖,一样错位。

05 裸日志解析:自己带一份"会演进的字典"

换个思路:既然错位的根子是"字典只有当下这一份",那就把字典也当成需要持续演进的对象来管。

  1. DML + DDL 一起采:不只解析行变更,DDL 也当事件捕获,沿时间线维护一份会演进的结构信息;
  2. 按"日志产生时"的结构还原:解析某一段日志,用的是那一段对应时刻的结构,而不是今天的;
  3. schema 变更平滑过渡:加列、改类型、删列这些动作,链路自己消化,不把问题甩给下游;
  4. 粒度可控:表、列的过滤与映射,由自己掌握。

这就是"直接读二进制"能干、而"套数据库接口"干不了的事——结构信息从日志里来,字典自己维护,不依赖数据库当下的状态。


06 反直觉的一点

直觉上,"字段变多了"是信息更丰富了,同步应该更容易才对。

但在 Oracle 日志解析这条路上,它反而是一个高风险动作。原因很别扭:日志记的是编号,不是名字;编号对应的名字,取决于你"问谁、什么时候问"。翻译字典的时效性,比字段本身更重要。


07 说句实在话

schema 演进做得好不好,平时看不出来——直到业务真加了个字段。

这是很多团队上线时不会重点测、上线后一定会踩的一环。我们的选择是:把结构演进的账自己算清楚,让"加个字段"回到它本来的样子——一件小事。


往期回顾

  • (十一)只想采一个 PDB,却要牵连整个 CDB
  • (十二)存储堆到顶配,为什么还是慢
  • (十三)想从备库采数,为什么要先动容灾
  • (十四)为了采数据,为什么要先动生产库的配置
  • (十五)给表中间加一列,为什么能把同步链路搞挂 ← 本篇

声明:文中 DDL 支持范围引自 Debezium 官方文档的 Oracle DDL 支持说明与 Flink CDC 社区讨论;LogMiner 使用"当前数据字典"翻译日志对象的机制,可参考 Oracle 官方 Database Utilities 手册。实际表现受数据库版本、DDL 类型与下游实现影响,请以自身环境实测为准。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进