
做数据同步这些年我越来越觉得“MySQL 数据出海”是个特别形象的说法。业务库里攒了几百G的数据业务系统一套链路数据分析、报表、数仓、算法那边眼巴巴等着用可数据还困在生产库里出不去——这就是典型的“数据出海”场景。核心问题是怎么把 MySQL 的数据安全、准确、低延迟地同步到目标端比如数据仓库、分析型数据库、消息队列或者另一个机房的主从集群。这篇文章围绕 MySQL 数据同步方案把选型思路、主流方案的底层逻辑、两套完整实操GTID 主从同步、Flink CDC 同步到 ClickHouse和一堆真实踩坑记录都梳理出来。不管你是 DBA、后端研发还是刚接触数据同步的运维同学都能在里面找到可以直接照搬的办法也能理解每一步为什么要这么做。这几次同步方案选型我是真金白银踩过坑的下面写的东西都是我实际跑过的链路照着做基本能稳。1. 先说清楚“出海”到底要解决什么问题1.1 你以为的同步和我说的同步不是一回事很多人一听“数据同步”第一反应就是“把 MySQL 数据拷一份到另一个 MySQL”。这确实是常见需求但只是出海方向里的一个分支。我实际接手过的项目里MySQL 数据往外走的方向大概有这几类业务库 → 数据分析库/数据仓库比如 ClickHouse、Hive、StarRocks典型诉求是聚合查询、BI 报表、用户画像业务库 → 下游业务系统比如订单数据同步给搜索服务、推荐服务典型诉求是分钟级或秒级延迟且不能丢数据业务库 → 消息中间件Kafka、RocketMQ典型诉求是流式消费、事件驱动架构生产主库 → 灾备从库/异地从库典型诉求是 RPO 尽可能低丢数据范围越小越好旧系统 MySQL → 新系统 MySQL典型诉求是停机窗口内完成全量增量切换数据要一致。这些场景看起来都是“从一个 MySQL 到另一个系统”但同步方案完全不同。你做灾备用 binlog 主从复制或者物理备份恢复就够了你做数据分析就要考虑维度建模、字段类型映射、全量增量合并的问题你做下游业务消费就得接 CDC 工具把 binlog 流式往外推。所以第一步永远是问清楚目标系统是谁、数据用来干什么、能接受的延迟和丢失量是多少。这三个问题确定之前选任何方案都是耍流氓。1.2 数据同步链路的四个关键环节不管选什么方案一条完整的数据同步链路拆开来都是四个环节采集、传输、写入、校验。采集决定你能拿到什么粒度、什么准确度的数据。比如直接查表还是读 binlog是全量扫描还是只拿增量这决定了同步的基础能力。传输环节考验的是网络带宽、延迟和吞吐尤其跨机房或者跨云场景带宽有限压缩和并发策略就特别重要。写入环节直接关系到目标库的性能和一致性是批量写入还是流式写入、有没有做幂等、字段类型映射对不对全在这一步出问题。校验是最容易被人忽略的数据过去了到底对不对行数对不对最新一条对不对极值对不对没有校验的同步链路就是在裸奔。我在做方案的时候习惯把四个环节分别列出来每个环节标注清楚“用什么工具/策略实现”这样整个链路就清晰了。比如主从同步采集binlog dump 线程→ 传输主从库之间的网络协议可压缩→ 写入SQL 线程重放 relay log→ 校验可以用 pt-table-checksum 定期核对。以后排查问题也能顺着环节一步步缩小范围。1.3 影响范围一次同步方案改动往往牵动整个系统还有一个容易被低估的地方同步方案不是只影响数据搬迁本身。接入了 CDC 之后binlog 保留时间可能要调整原来 2 小时现在要留 3 天主库负载也要监控因为多了几个 dump 客户端目标库的下游任务依赖链路只要同步延迟几十分钟下游报表就可能全部跑偏。我做过一次最典型的例子把 MySQL 同步到 ClickHouse 之后分析师写的查询从几百秒优化到几百毫秒但背后如果同步链路断了报表就是一套过期数据比没有数据还可怕。所以数据同步方案本质上是一个“稳定性工程”不只是选个工具。下面我从方案选型开始逐步讲清楚我实际使用的工具和配置。2. 主流 MySQL 数据同步方案横评2.1 五种方案各自适合什么场景市面上的方案看着五花八门扒开底层其实就是五条路第一逻辑导出导入。mysqldump、mydumper这类工具把表结构和数据以 SQL 或文本格式导出来再灌进目标库。特点是比较通用、实现简单适合小数据量比如说几 GB 以内的一次性迁移。缺点也很明显性能差、对源库有额外压力、增量同步做不了。第二物理备份恢复。典型代表是XtraBackup。它直接拷贝 InnoDB 数据文件配合 redo log 做到一致性备份。恢复的时候把文件放回去再启动 MySQL 就行。适合大数据量几十 GB 到几 TB的全量初始化比如新建从库、迁移机房。它不产生逻辑 SQL速度比 mysqldump 快一个量级。第三主从复制。这是 MySQL 官方最成熟的能力基于 binlog 实现。从库通过 I/O 线程拉取主库 binlog写到本地 relay log再由 SQL 线程重放。适合 MySQL 到 MySQL 的同步能做实时、能做灾备还能做读写分离。历史上有基于文件位置的复制现在强烈建议用 GTID 复制后面实操部分细说。第四CDC 工具链。典型代表是 Canal、Debezium、Flink CDC。它们伪装成 MySQL 的从库解析 binlog 然后把数据转成结构化事件比如 JSON推到 Kafka 或者由客户端消费。特点是解耦、实时性好秒级、支持跨系统同步——MySQL 的数据可以同步到 ClickHouse、Elasticsearch、Redis、Hive 等等。这是“数据出海”方案里现在最主流的一条路。第五ETL 批量同步工具。比如 DataX、Sqoop、Kettle。它们更偏向批处理定时抽取、转换、加载。适合离线数仓场景不需要秒级实时性。注意 Sqoop 现在更新极慢连接 MySQL 的驱动也经常出问题这个我后面排坑部分会提到。2.2 方案选型决策表我列一个自己常用的决策表核心看三个维度数据量、实时性、源和目标是否同类。方案典型工具全量能力增量能力延迟适用场景逻辑导出mysqldump / mydumper强无-小表迁移、结构同步物理备份XtraBackup强TB级借助binlog补增量-新建从库、全量初始化主从复制MySQL原生复制弱需初始化强秒级秒级以内MySQL → MySQL 实时同步CDC工具链Canal / Debezium / Flink CDC中需配合全量强秒级秒级MySQL → 异构系统实时同步ETL批量同步DataX / Sqoop强中定时分钟~小时离线数仓、T1数据我个人的选型口诀是同构实时用主从异构实时用 CDC离线批处理用 ETL一次性初始化用备份/导出。很多人一上来就想上 Flink CDC结果场景其实只需要主从复制这是典型的过度设计。反过来如果需求是实时报表且目标库是 ClickHouse你还硬要做成“MySQL 主从复制 业务侧双写”那就更折腾了直接 Flink CDC 一条链路顶上去反而简单。3. 实操一基于 GTID 的主从同步从备份到从库接管3.1 为什么我坚持用 GTID 而不是传统位点复制老一代 DBA 可能都用过CHANGE MASTER TO MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POS107这种方式。传统位点复制最大的痛点在于File和Pos是两个极其脆弱的状态。主库一切换、从库一重建、日志一清理位置就对不上了。你得手工去翻 binlog 找位点出错概率极高。GTIDGlobal Transaction Identifier相当于给每笔事务发了一个全局唯一的身份证号由source_id:transaction_id组成。从库不需要记录读到了哪个文件哪个偏移量只需要告诉主库“我已经执行过哪些 GTID剩下的请发给我”。主库自动跳过已执行事务天然避免了重复执行和漏执行的问题。在多级复制、主主复制、failover 场景下这个优势直接救命。当然 GTID 也有限制比如一个事务中不能同时修改事务表和非事务表CREATE TABLE ... SELECT这类语句要改用先建表再插入的方式。但在实际生产环境里这些限制完全可以通过规范 SQL 写法来规避收益远大于成本。MySQL 5.7 开始 GTID 已经非常稳定不需要犹豫。3.2 用 XtraBackup 完成从库全量初始化假设主库 A 上有个库orders数据量有 400G我要在 B 服务器上建一个从库。如果直接用mysqldump全量导出那 400G 可能要跑几个小时源库 I/O 也被拖垮。所以我从 XtraBackup 的全量物理备份开始做。第一步在主库创建备份账号并授权。需要注意权限不只是RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT这些还得有BACKUP_ADMIN权限MySQL 8.0 里对应BACKUP_ADMIN5.7 里可以给SUPER。CREATE USER backup% IDENTIFIED BY Backup123; GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT, BACKUP_ADMIN ON *.* TO backup%; FLUSH PRIVILEGES;第二步执行备份。我最常用的命令是xtrabackup --backup \ --userbackup \ --passwordBackup123 \ --host127.0.0.1 \ --port3306 \ --target-dir/data/backup/full_$(date %F) \ --compress \ --compress-thread-count8 \ --parallel8这里有两个关键参数要说清楚。--parallel是打开多个线程并行拷贝 InnoDB 数据文件能显著缩短备份时间但要注意磁盘 I/O 是否会成为瓶颈我一般先跑一个小库压测一下再定并发数。--compress会把数据文件压缩后写入备份目录对网络传输友好但是恢复的时候需要解压会多一些 CPU 开销。如果备份目录就在本地磁盘且后续是内网拷贝也可以不压缩。第三步备份完成之后把备份集拷贝到从库 B。可以用scp或者rsync我习惯用rsync支持断点续传几 TB 的备份集拷贝更稳。rsync -avP /data/backup/full_2025-01-01/ userserver-B:/data/backup/full_2025-01-01/第四步在从库上做恢复准备。重点来了XtraBackup 备份出来的数据文件不能直接启动 MySQL因为备份过程中 InnoDB 的数据文件之间存在不一致需要先应用 redo log 进行崩溃恢复这个动作对应--prepare老版本叫--apply-log。如果备份时加了压缩还需要先解压xtrabackup --decompress --target-dir/data/backup/full_2025-01-01/ xtrabackup --prepare --target-dir/data/backup/full_2025-01-01/--prepare跑完后备份目录里的数据文件就是一致性的了。然后把配置好的 MySQL 数据目录清空把备份文件移动到数据目录# 在从库 B 上默认 datadir/var/lib/mysql 或 /data/mysql systemctl stop mysql mv /data/mysql /data/mysql_bak_$(date %F) mkdir -p /data/mysql chown mysql:mysql /data/mysql xtrabackup --move-back --target-dir/data/backup/full_2025-01-01/ chown -R mysql:mysql /data/mysql systemctl start mysql这时候从库 B 上的数据就是主库 A 在备份时刻的完整快照而且因为 XtraBackup 备份时自动记录了备份点的 binlog 位点和 GTID 信息我们可以流畅地接入主从复制。3.3 配置 GTID 主从复制三步完成从库 B 启动后先在主库 A 上确认gtid_mode和enforce_gtid_consistency是开启的[mysqld] gtid_modeON enforce_gtid_consistencyON log_binmysql-bin binlog_formatROW server-id1MySQL 5.7 之后这些参数可以动态开启但gtid_mode从 OFF 切到 ON 需要经过一个 permissive 热身阶段直接改配置文件重启一般在生产环境也够用。从库 B 的配置文件注意server-id一定不能和主库相同建议和 IP 最后一段对应方便排障。然后执行-- 从库 B CHANGE MASTER TO MASTER_HOST192.168.1.10, MASTER_USERrepl, MASTER_PASSWORDRepl123, MASTER_AUTO_POSITION1; START SLAVE;MASTER_AUTO_POSITION1是关键它让从库自动用 GTID 和主库对齐不需要指定MASTER_LOG_FILE和MASTER_LOG_POS。执行后查看状态SHOW SLAVE STATUS\G重点看三行Slave_IO_Running: Yes—— I/O 线程正常拉取 binlogSlave_SQL_Running: Yes—— SQL 线程正常重放事务Seconds_Behind_Master: 0—— 从库延迟为 0。如果 IO 线程显示Connecting先检查主库有没有创建复制账号授权。创建复制账号的命令是CREATE USER repl192.168.1.% IDENTIFIED BY Repl123; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl192.168.1.%; FLUSH PRIVILEGES;3.4 这套方案要注意的三个坑第一个坑XtraBackup 版本必须和 MySQL 版本匹配。用 8.0 的 xtrabackup 去备份 MySQL 5.7 可能会直接报错备份出来的文件在 prepare 阶段就说“corrupted”。我吃过一次亏备份跑了 2 小时最后在 prepare 阶段全废。现在我的规矩是xtrabackup 大版本比 MySQL 大版本高一个以内可以兼容但小版本尽量贴近。第二个坑别在业务高峰期跑 XtraBackup。虽然它是物理备份对业务影响比锁表导出小很多但毕竟要读取所有数据文件磁盘 I/O 会被打满高峰期容易引起主库慢查询。我一般安排在凌晨 2 点到 6 点之间跑。第三个坑主库的 binlog 保留时间足够长。从库如果断连太久比如停机维护了三天主库 binlog 却只保留 24 小时那从库重连时会发现需要的 binlog 已经清理了只能重新全量初始化。建议先确认expire_logs_daysMySQL 8.0 是binlog_expire_logs_seconds设置同步场景我通常设置保留 3 天以上。4. 实操二用 Flink CDC 把 MySQL 同步到 ClickHouse4.1 为什么异构同步首选 Flink CDC如果你要把 MySQL 的数据实时同步到 ClickHouse、ES、Redis 这类非 MySQL 系统主从复制就不灵了因为它只认 MySQL 协议和 MySQL 存储引擎。这时候 CDCChange Data Capture就是唯一靠谱的路。市面上 CDC 工具有 Canal、Debezium、Flink CDC。我为什么推荐 Flink CDC三个理由。第一Flink CDC 直接基于 Flink 框架天然支持分布式、Checkpoint、状态管理这意味着同步任务可以容错恢复不会因为网络抖动就丢数据。第二Flink CDC 把“全量增量”一体化了任务启动时先做全量快照快照完成后自动切到 binlog 增量中间不需要你手工拼接这一点比 Canal 自己写全量脚本省太多事。第三Flink SQL 的声明式写法足够友好不需要写一堆自定义代码同步链路用 SQL 就能描述。4.2 版本搭配和任务设计思路我用的版本组合是 Flink 1.18 Flink CDC 3.1连接器flink-cdc-pipeline-connector-mysql。这里特别提醒一下Flink CDC 3.x 和 2.x 在使用方式上差别很大2.x 是在 DataStream API 里写SourceFunction3.x 推出了 YAML pipeline 模式配置一个 pipeline 文件就能启动同步任务不用写 Java 代码部署效率高很多。我的架构设计是Flink CDC 从 MySQL 读取 binlog → 经过 Flink 处理 → 写入 ClickHouse。目标表按业务场景选引擎如果是明细查询用 MergeTree 按主键去重如果只是聚合统计可以放在 ReplacingMergeTree 或 SummingMergeTree。同步延迟目标控制在 5 秒以内。任务核心是写一个 pipeline 配置文件类似这样source: type: mysql hostname: 192.168.1.10 port: 3306 username: cdc_user password: Cdc123 tables: orders.order_detail server-id: 5400-5404 sink: type: clickhouse hostname: 192.168.1.20 port: 8123 username: default password: ClickHouse123 tables: order_detail num-commit-threads: 4 pipeline: parallelism: 4配置文件里最有讲究的是server-id。Flink CDC 模拟的是 MySQL 从库每个并行度都要占用一个 server-id范围要够用且不能和现有从库或者其他 Canal 客户端冲突。我之前遇到过一个诡异故障任务起不来报TimeoutException排查到最后发现就是 server-id 和另一个环境的 Canal 撞了。原则就是给每个同步任务分配一段独立 server-id 区间比如 5400-5404不要所有任务共用同一个 ID。4.3 启动任务前的表结构和字段映射准备真正让同步任务跑起来之前有一半的功夫花在表结构和字段映射上。MySQL 和 ClickHouse 的类型体系差异很大不能直接照搬。我经常用的映射规则如下MySQLINT→ ClickHouseInt32BIGINT→Int64MySQLVARCHAR→ ClickHouseStringMySQLDECIMAL(10,2)→ ClickHouseDecimal(10,2)这个没问题MySQLDATETIME、TIMESTAMP→ ClickHouseDateTime64(3)带毫秒的数据不会丢精度MySQLDATETIME(6)→ ClickHouseDateTime64(6)微秒精度也要对齐MySQLTINYINT(1)→ ClickHouseBool或者Int8这里容易出错如果业务把 TINYINT(1) 当作枚举值用映射成 Bool 就废了MySQLJSON→ ClickHouseStringClickHouse 的 JSON 类型支持不如 MySQL 成熟存成 String 再让下游解析更保险。如果使用 Flink SQL 模式可以用CREATE TABLE语句声明源表和目标表然后用一条INSERT INTO完成同步。这种方式在字段较多、经常加列的场景下维护成本高一些我倾向于在 pipeline 配置里直接做映射。两种方式我都跑过YAML pipeline 胜在配置简单Flink SQL 胜在灵活可以中间加 filter、join、聚合算子。4.4 Checkpoint 配置和数据一致性保障CDC 任务最怕的故障是同步到一半MySQL 主库切了、网络闪断、任务 OOM。如果没做 Checkpoint重启后可能重复读或者漏读一段 binlog导致数据错乱。Checkpoint 是 Flink 的容错机制简单理解就是给整个同步任务的状态打快照记录“已经同步到哪个 binlog 位点了”故障后自动从最近一个完成的快照恢复。在 Flink 配置里开启execution.checkpointing.interval: 60s execution.checkpointing.mode: EXACTLY_ONCE execution.checkpointing.min-pause: 30s execution.checkpointing.timeout: 120sEXACTLY_ONCE模式会保证数据不丢不重代价是会有一些额外的延迟和资源开销。对绝大多数报表同步场景我建议先开AT_LEAST_ONCE配合目标端幂等因为 ClickHouse 的 ReplacingMergeTree 天然支持按主键去重重复数据到目标端会被合并掉。这样延迟更低运维更省心。启动任务后如何判断链路真的通了我一般跑三遍验证在源库执行任意一条 DML比如UPDATE order_detail SET status2 WHERE id1235 秒内去 ClickHouse 查同一行应该已经看到变更全量数据行数对齐SELECT COUNT(*) FROM MySQL表和SELECT COUNT(*) FROM ClickHouse表比对找一个时间字段比如update_time取两边 max 值对比差距应该在秒级。5. 同步链路落地之后那些踩过的坑和排查实录5.1 GTID 复制中断的两种典型故障GTID 主从跑了几个月最容易遇到的是Last_SQL_Errno: 1236或者Last_SQL_Errno: 1045这类错误。1236通常是“从库尝试拉取的 binlog 已经不存在了”原因基本是主库日志清理过快或者主从断连太久。处理办法是如果确认从库已经追不上干脆停止复制重新用 XtraBackup 做一次全量初始化这是最省力气的方式。1045则多半是复制账号权限被误改了重新GRANT REPLICATION SLAVE并执行FLUSH PRIVILEGES即可。我特别想提醒一点出问题第一件事是看SHOW SLAVE STATUS\G的Last_IO_Errno和Last_SQL_Errno不要瞎猜。很多人在从库上执行了写操作导致 SQL 线程报主键冲突这种问题直接跳过那一条事务继续即可但我建议先查清楚是哪条 SQL 冲突了有真实业务意义才行不能盲目SET GLOBAL sql_slave_skip_counter1一路跳过会把数据一致性彻底搞砸。5.2 锁表问题备份和同步为什么会拖垮业务“同步/备份导致业务锁表”是热搜里出现频率很高的问题也是新人最容易翻车的点。mysqldump默认会加全局读锁FTWRL在业务高峰期跑全量导出等于让所有写操作排队这是绝对不能忍的。解决办法是加--single-transaction参数让 dump 在 RR 隔离级别下通过一致性视图读取数据不加锁。但这个参数只对 InnoDB 表有效如果你的库里有 MyISAM 表依然会锁。XtraBackup 也会在备份开始时短暂加锁但它通过 redo log 机制把锁的持有时间缩短到秒级甚至毫秒级所以对业务影响远小于 mysqldump。如果你发现 XtraBackup 运行期间大量Waiting for global read lock出现检查是不是备份账号缺少BACKUP_ADMIN权限或者实例上有 DDL 没跑完。再说一个容易被忽略的坑Flink CDC 全量阶段默认会执行FLUSH TABLES WITH READ LOCK来获取一致性快照位点这一步也会产生瞬间锁表。虽然持续时间很短但如果你的源库有长事务快照位点拿不到锁就释放不了。我踩过这个坑之后在源端增加了一个监控全量初始化期间盯住SHOW PROCESSLIST里有没有Waiting for global read lock如果超过 5 秒就手动干预。5.3 MySQL 和 ClickHouse 数据类型不一致导致的同步报错同步跑着跑着突然报错最常见的原因就是类型映射没做好。举个真实例子MySQL 里一张表的status字段是TINYINT(1)本来存的是 0/1/2我映射到 ClickHouse 的Bool结果 2 被写入时报错Bool 只能存 0/1。还有一个例子是DATETIME里的零值0000-00-00 00:00:00MySQL 能存但 ClickHouse 的DateTime64不接受同步任务直接卡死。处理这类问题的思路是在建表阶段就把目标端的字段类型设计得比源端更宽容。TINYINT统一用Int8DATETIME零值在同步 SQL 里通过NULLIF(col, 0000-00-00 00:00:00)转成NULL。在 Flink CDC pipeline 里可以加一条过滤/转换规则但最省心的方式还是源头 SQL 写好转换逻辑。5.4 Sqoop 连接不上 MySQL 的经典排查流程虽然我现在很少用 Sqoop 做同步了但很多老项目还在跑。网上关于“sqoop连接不上mysql”的求助帖特别多我帮人排查过几轮问题几乎都集中在这几个点驱动问题。Sqoop 自带的数据库驱动版本太老连接 MySQL 8.0 直接报Unable to load authentication plugin caching_sha2_password。解决方法是下载mysql-connector-java 8.0.x放到$SQOOP_HOME/lib下。主机名解析。MySQL 端授权的 hostname 和你 sqoop 连接的 hostname 不一致会报Access denied for user。建议统一用 IP 连接并授权。时区参数。MySQL 8.0 默认时区设置会导致连接报The server time zone value ... is unrecognized在 JDBC URL 后面加?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue基本能解决。防火墙和端口这个不需要多说telnet 一下 3306 端口就知道通不通。顺便说一句数据量大的时候 Sqoop 性能并不好如果你还在设计新方案尽量避免选它。同类的 DataX 在同步性能、断点续传和插件生态上都要好不少而且 DataX 对 MySQL 到 ClickHouse 这类异构库也支持得不错。5.5 监控和报警如何提前发现同步异常同步链路最怕的不是出故障而是故障出了很久没人发现。我维护过的所有同步任务都会加四层监控延迟监控SHOW SLAVE STATUS里的Seconds_Behind_Master在 Flink 里可以用metrics监控当前 binlog 位点与源库最新位点差。延迟超过 30 秒就报警超过 5 分钟就呼叫值班人。数据量校验每个小时统计一次源表和目标表的行数差值超过阈值直接告警。大表用max(id)或者count(*)抽样即可没必要全表 count。binlog 堆积监控主库的 binlog 文件数如果增长异常说明消费端出了问题或者 binlog 保留时间太短这是最容易被忽略的信号。任务心跳Flink 任务本身如果挂了Source 端没有新事件很有可能表面看起来一切正常。建议给目标端写入一个心跳表每 10 秒更新一次now()监控端查这张心跳表的时间戳就能判断任务是否活着。这个监控体系救过我很多次。有一次凌晨三点 Flink Checkpoint 一直超时延迟从几秒飙到几十分钟如果没有延迟告警早上分析师看到的报表就全是滞后数据。6. 个人体会和一点扩展建议写到这里我想把最核心的经验再浓缩一遍。做数据同步方案选型永远比工具操作重要。拿到需求先问清三个问题数据去哪、延迟多少、能容忍多大误差。然后把这篇文章里的决策表拿出来对一遍大概率能选到最合适的路。我自己的偏好很明确小数据量一次性迁移用mysqldump --single-transactionTB 级全量初始化用 XtraBackupMySQL 到 MySQL 长期同步用 GTID 主从MySQL 到异构系统实时同步用 Flink CDC离线 T1 数仓用 DataX。这套组合拳我跑了两三年稳定性维持在 99.9% 以上。最后分享一个小技巧任何同步方案上线前一定先把“应急预案”写了。主从挂了怎么办、CDC 任务卡死怎么办、目标库磁盘满了怎么办每一步给到一个可以执行十分钟内的恢复命令。我把这些命令直接写进运维脚本出了问题不用翻文档跑一下脚本就能恢复。数据同步这条链路真正拼的不是同步技术本身而是当它出问题时你能否冷静、快速、正确地把数据挽救回来。希望这篇分享能帮你少踩几个坑。