mysql增量实时迁移需满足“不丢、不错、可重放”,核心前提是开启row模式binlog、正确配置canal参数及下游幂等消费,且必须配合全量数据补录才能构成完整迁移方案。

Canal + MQ 实现 MySQL 增量实时迁移,核心在于“不丢、不错、可重放”——只要 MySQL 开启了 ROW 模式 binlog 且 Canal 能稳定连接,就能做到毫秒级延迟;但一旦配置错一个参数,比如 canal.instance.filter.regex 写漏了库名或用了错误转义,下游就收不到任何变更。
MySQL 必须开启 ROW 模式 binlog 并配好权限
Canal 不支持 STATEMENT 或 MIXED 模式,只认 ROW。否则解析出来的事件缺少完整字段,下游写 ES/Redis/ClickHouse 时会丢数据甚至报错。
-
log-bin和server-id必须在[mysqld]下启用,且server-id全局唯一(多个 Canal 实例共用同一 MySQL 时尤其注意) -
binlog-format=ROW是硬性要求,别信“MIXED 也能跑”的旧文档 - 账号权限必须包含
REPLICATION SLAVE和REPLICATION CLIENT,仅SELECT不够——Canal 需要拉取 binlog position,不是查表 - 验证命令:
SHOW VARIABLES LIKE 'log_bin';、SHOW VARIABLES LIKE 'binlog_format';、SHOW MASTER STATUS;缺一不可
Canal 实例配置里最常踩的三个坑
instance.properties 看似简单,但每行都影响数据是否能被正确捕获和路由。
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
-
canal.instance.master.address必须写 MySQL 实际监听地址(如192.168.1.100:3306),不能写localhost或127.0.0.1(Docker 容器内网络不通) -
canal.instance.filter.regex是正则表达式,不是 glob。想同步order_db和user_db,得写成order_db\..*,user_db\..*,漏掉双反斜杠\.就匹配失败 -
canal.mq.topic和canal.mq.dynamicTopic要配合使用:设为true时,topic 名由表名动态生成(如order_db.product);设为false则所有变更发到固定 topic(如binlog-all),下游需自行解析库表信息
Kafka/RabbitMQ 消费端必须处理重复和乱序
MQ 层不保证严格有序,Canal 发送也非事务性——单条消息可能重发,也可能因网络抖动导致后发的 update 先到。下游消费者不能假设“先 insert 后 update”一定按顺序抵达。
- 用
BinlogMessage中的timestamp和entry.getHeader().getExecuteTime()做时间排序(注意:MySQL 服务端时间可能有漂移) - 对同一主键的变更,用
eventKey = tableName + ":" + row.get("id")做幂等写入(RedisSET带过期、ESindex操作天然覆盖) - 不要依赖 Kafka 的
partition保序:Canal 默认按表 hash 分区,但跨表操作(如订单+订单项)仍会跨 partition,无法保证事务一致性 - RabbitMQ 若用
fanoutexchange,必须自己做 routing key 解析;Kafka 更推荐用canal.mq.dynamicTopic=true+ 多 topic 订阅,降低单 consumer 负载
增量迁移 ≠ 全量迁移,首次启动前必须补全历史数据
Canal 只捕获启动后的变更,不会回溯已有数据。如果你直接起 Canal 然后往 ES 写,那 ES 里只有新订单,没有老订单——这根本不是“迁移”,只是“增量管道”。
- 全量阶段:用
mysqldump --skip-triggers --no-create-info --single-transaction导出数据,再用批量工具(如 logstash、自定义脚本)导入目标系统 - 衔接点:记录全量导出完成时刻的
SHOW MASTER STATUS输出(File和Position),填入 Canal 的canal.instance.master.journal.name和canal.instance.master.position - 跳过重复:全量导入时,目标系统(如 Redis)需设置 TTL 或用
SETNX避免覆盖 Canal 后续写的实时数据
真正难的不是让 Canal 跑起来,而是确认每一条 delete 是否被消费、每一个 update 是否没被覆盖、跨库关联字段在目标系统里是否始终一致——这些没法靠配置解决,得靠下游 consumer 的健壮逻辑和可观测性(比如每分钟统计 Canal 拉取 event 数 vs MQ 接收数 vs ES 成功索引数)。










