原创

MySQL MINIMAL 行镜像:CDC 缺失列不能当成 NULL

CDC 消费到一条商品更新,库存有新值,商品名却没有出现。若源库使用 ROW 日志格式,同时把 binlog_row_image 改成了 MINIMAL,这个事件很可能只是合法的稀疏更新,不是一条字段齐全的商品快照。

缺失列不能直接解释成 NULL。下游若拿这份事件整行覆盖搜索索引、缓存或宽表,原本未修改的字段就可能被抹掉。日志大小减少了多少是后面的问题,先核对下游是否理解这种载荷。

用一列变化区分完整镜像和稀疏镜像

下面只在启用二进制日志的隔离 MySQL 8.4 实例操作。需要拥有设置受限会话变量及读取日志的实验权限,不修改全局参数。

CREATE DATABASE row_image_lab;
USE row_image_lab;
CREATE TABLE catalog_item (
    id BIGINT NOT NULL PRIMARY KEY,
    sku VARCHAR(30) NOT NULL,
    item_name VARCHAR(100) NOT NULL,
    stock INT NOT NULL,
    note TEXT NULL,
    UNIQUE KEY uk_sku (sku)
) ENGINE=InnoDB;
INSERT INTO catalog_item
VALUES (7, 'SKU-7', 'blue mug', 100, 'keep this text');

SELECT @@session.binlog_format, @@session.binlog_row_image;
SHOW BINARY LOG STATUS;

先确认当前会话以 ROW 格式记录本实验更新。不同日志格式下,这个变量的作用不能混为一谈。若实例没有启用二进制日志,需要按实验环境配置重启,而不是以为设置行镜像变量会自动生成日志。

在同一会话分别做两次更新,每次之前和之后记下日志文件与位置:

SET SESSION binlog_row_image = 'FULL';
SHOW BINARY LOG STATUS;
UPDATE catalog_item SET stock = 99 WHERE id = 7;
SHOW BINARY LOG STATUS;

SET SESSION binlog_row_image = 'MINIMAL';
SHOW BINARY LOG STATUS;
UPDATE catalog_item SET stock = 98 WHERE id = 7;
SHOW BINARY LOG STATUS;

这里没有给出伪造的日志位置。位置由实例当时的日志决定,必须记录自己的输出,才能精确截取对应事务。

在隔离环境复制出的日志文件上,用与服务端兼容的 mysqlbinlog 解码。路径替换为实际实验文件,并按记录的位置增加 --start-position、--stop-position:

mysqlbinlog --base64-output=DECODE-ROWS --verbose --verbose \
  ./binlog.000001 > decoded.txt

--verbose 输出的行事件注释用于观察,不应当作可以直接拿来替代原始日志重放的完整业务 SQL。查看列编号时,对照当时的表定义,而不是凭当前字段顺序猜测。

MINIMAL 省掉的是哪些列

对有主键的这张表,更新前镜像可以主要提供识别旧行所需的主键;更新后镜像记录这次语句指定修改的相关列。商品名、SKU 和说明没有改动,不应要求它们在每次更新事件中都重复出现。

插入只有新行镜像,删除只有旧行镜像,更新才需要比较两面。因此不能把“每条事件都有 before 和 after”写死在统一解析器里,更不能把没有 after 的删除事件当空对象更新。

FULL、MINIMAL 与 NOBLOB 是不同的行镜像策略。另一个名称很像的 binlog_row_metadata 控制元信息多少,不是把稀疏数据补回完整行;JSON 部分更新还有自己的配置边界。排查时把变量名、日志格式和事件类型一起保存,比只截一张配置截图有用。

为什么更新事件到达后,再查一次源库不一定能补对

假设日志事件对应库存从 99 改为 98,下游消费前商品名又被后续事务修改。此时回源查询拿到的是查询时刻的数据,不一定是这个事件提交时的完整状态。把它当成旧事件的完整新行,会混合两个时间点。

有状态的下游可以在已知基线上应用增量,但基线必须与日志位置连续衔接,并处理删除、重建、重复投递和顺序。没有可靠基线的“拿到什么字段就补什么”,遇到消费者重启或历史数据缺口时,会出现难以识别的半行状态。

如果使用现成 CDC 连接器,应以该连接器明确支持的源库配置为准。例如一些连接器要求 FULL 行镜像来形成完整变更记录,不能因为数据库原生复制支持 MINIMAL,就推断所有外部消费工具也支持。原生副本有自己的旧行状态,外部系统可能从中途位置开始,两者前提不同。

变更之前,应该验证恢复和重放契约

一次受控验证至少要包含只改一列、修改主键、删除、带 NULL 的更新和重放同一事件。对照下游最终行内容,而不是只看“连接器没有报错”。解析器能读懂事件,不代表它能恢复正确业务状态。

还应确认配置生效范围。本实验只改变会话变量,全局修改对已有会话与新建会话的影响需要按实际版本与连接生命周期检查。应用连接池如果长期保留连接,可能出现同一时间不同写入连接产生不同镜像的窗口。下游不能假定某个变更时刻之后每一条事件都已经统一切换。

切换回 FULL 也不会补齐已经产生的历史稀疏事件。若消费端已经把缺失列当 NULL 写坏,需要定位受影响的日志范围和对象,再用受控校正或重建修复;只恢复配置并重启消费者,不构成数据恢复。

空间、网络与解码成本当然值得衡量,但省日志必须服从下游契约。对只有原生复制的系统和对接多个 CDC 消费者的系统,正确选择可能不同。先画清每个消费者从哪里获得完整基线,再决定哪些列可以不记录。

正文到此结束
评论插件初始化中...
Loading...
本文目录