今天在处理一套 MySQL 8 同步到 Oracle 19C 的 OGG 时,发现复制进程突然不跑了。Replicat 一启动就退出,日志里反复刷同一个错误:OGG-00918 Key column ID is missing from map。第一反应并不是去翻 OGG 文档,而是先回忆最近有没有动过表结构,因为这种 key column 相关的报错,十有八九和表变更脱不开关系。

顺着这个思路去对了一下,果然发现开发前一天在目标端的 Oracle 表上加了一个 ID 列,用的是 12c 之后支持的 IDENTITY 自增列。表面看起来这事挺“合理”的,毕竟业务只是在目标端加个自增主键,又没动源端 MySQL,按常规理解也不应该影响同步。但问题往往就出在这种“看起来没问题”的地方。
Oracle 的 IDENTITY 列虽然在语义上是数据库自动生成,但在表结构层面,它就是一个隐式 NOT NULL 的普通列。OGG 并不会理解“这个列是自增的、不需要你管”,它只会老老实实按自己的规则去判断:这个表有没有主键?有没有唯一键?如果没有,就挑一些它认为“合适”的列来当 key。很不巧,这个新加的 ID 列正好是 NOT NULL,于是被 OGG 选中了。
问题随之而来。源端 MySQL 根本没有这个列,trail 里自然也不会有 ID 的值。Replicat 在解析 MAP 的时候,发现目标表需要用 ID 来定位行,但 MAP 里、trail 里都拿不到这个字段,于是直接报了 OGG-00918,拒绝启动。站在 OGG 的角度看,这个逻辑完全说得通,它确实没法定位行。
到这一步,原因其实已经很清楚了:不是 OGG 坏了,也不是配置写错了,而是目标端新增的 IDENTITY 列打破了原本隐含的同步假设。剩下要做的事情,就只是选一个“最不折腾”的解决方式。
源端表结构肯定不能动,业务也不可能为了 OGG 回退目标端的字段设计,那就只能在复制配置上下手。和业务简单确认了一下,挑了一组在业务逻辑上能唯一标识一行数据的字段,直接在 MAP 里显式指定 KEYCOLS:MAP ..., TARGET ..., KEYCOLS(col1, col2, ...);。这样一来,OGG 就不会再去关心那个新加的 ID 列,只按我们指定的字段来做行定位。

改完参数,重启 Replicat,进程顺利拉起,延迟开始回落,同步恢复正常。整个问题从发现到解决,其实没有用到什么“高级技巧”,更多是对 OGG 行为模型是否足够熟悉。
事后回头看,这类问题在运维里其实挺典型的:数据库本身的“新特性”和复制工具之间,并不一定是完全无感知的关系。尤其是 OGG 这种对表结构非常敏感的组件,目标端随手加一个 NOT NULL 列,都可能成为隐患。以后再遇到目标端要加 IDENTITY、自增字段这类需求,第一反应就应该是:OGG 会不会把它当成 key。
把这次经历记下来,也算给自己留个提醒,免得下次再在同一个地方踩坑。




