不能只靠 updated_at 字段做增量同步,因其易被误设、软删除不更新、多节点时钟不同步导致漏数据;binlog ROW格式才是可靠方案,需满足MySQL 5.7+、ROW模式、FULL镜像等前提。

为什么不能只靠 updated_at 字段做增量同步
直接用 SELECT * FROM t WHERE updated_at > '2024-01-01 00:00:00' 拉取增量,看似简单,但实际会漏数据。原因有三:
• updated_at 是应用层维护的,可能被误设为旧时间(比如手动 UPDATE 时写死值)
• 软删除、状态翻转等操作可能不更新该字段
• 多节点写入时,时钟不同步会导致时间乱序,拉取窗口遗漏或重复
MySQL binlog + ROW 格式才是可靠来源
binlog 的 ROW 格式记录每一行变更前后的完整镜像,天然支持 INSERT/UPDATE/DELETE 的精确捕获,且不依赖业务字段。启用前提:
• MySQL 5.7+,且 binlog_format = ROW
• binlog_row_image = FULL(默认值,确保 UPDATE 包含旧值)
• 用户需有 REPLICATION SLAVE 和 REPLICATION CLIENT 权限
• 启用 server_id(非 0),否则 binlog 不写入事件
SET GLOBAL binlog_format = 'ROW'; SET GLOBAL binlog_row_image = 'FULL';
如何安全地从 binlog 拉取增量(避开 GTID 和位点陷阱)
新手常卡在“从哪开始读”——用文件名+偏移量易出错,GTID 又要求主从拓扑一致。推荐折中方案:
• 首次同步:用 mysqldump --single-transaction --master-data=2 导出全量,并记录 CHANGE MASTER TO 对应的 binlog 文件和位置
• 增量消费:使用 mysqlbinlog 或客户端库(如 maxwell、canal)按 position 持续读取
• 关键约束:消费端必须自己持久化已处理的 filename 和 position,不能依赖 MySQL 自动推进
示例命令(跳过 DDL,只取 DML):mysqlbinlog --base64-output=DECODE-ROWS --verbose --start-position=12345 mysql-bin.000001 | grep -E "INSERT|UPDATE|DELETE"
如果硬要用 updated_at,至少加三道防护
某些遗留系统无法开 binlog,只能妥协。此时必须叠加校验:
• 每次拉取窗口向后延展 5 分钟(例如查 updated_at > '2024-01-01 00:00:00',但实际用 '2023-12-31 23:55:00' 作为起点)
• 对拉取结果按 updated_at 排序后,再检查最后一条记录的 updated_at 是否接近当前时间,若偏差过大则告警
• 每天跑一次全表 checksum(如 CRC32(GROUP_CONCAT(CONCAT(id,updated_at) ORDER BY id)))比对源和目标
注意:updated_at 字段必须是 NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,否则空值会导致条件失效
真正难的不是写 SQL 或配 binlog,而是让增量流在断连、重试、服务重启后仍能精准对齐 position,且不丢不重。这需要消费端自己管理位点状态,而不是指望 MySQL 记住你读到哪了。


















