mysql同步到clickhouse方案总览
| 方案 | 组件数 | 内存占用 | 实时增量 | UPDATE/DELETE | 维护状态 | 主要缺点 | 推荐度 |
|---|---|---|---|---|---|---|---|
| go-mysql | 1 | ~10MB | ✅ binlog | ✅ 支持 | 活跃 | 无Exactly-Once,社区小 | ⭐⭐⭐⭐⭐ |
| SeaTunnel | 1 | ~1-2GB | ✅ CDC | ✅ 支持 | 活跃 | 社区较新,文档不完善 | ⭐⭐⭐⭐ |
| Flink CDC + Kafka | 5 | ~4GB+ | ✅ binlog | ✅ 支持 | 活跃 | 组件多,资源消耗大,运维复杂 | ⭐⭐⭐⭐ |
| Debezium Server | 1 | ~300MB | ✅ binlog | ✅ 支持 | 活跃 | CK支持有限,需HTTP写入 | ⭐⭐⭐ |
| clickhouse-mysql-data-reader | 1 | ~200MB | ✅ binlog | ❌ 仅INSERT | 维护中 | 仅支持INSERT,无UPDATE/DELETE | ⭐⭐⭐ |
| Canal + Adapter + Kafka | 2-3 | ~1GB | ✅ binlog | ❌ 语法不兼容 | 停更 | Adapter停更,语法不兼容 | ⭐⭐ |
| Flink CDC 直连 | 2 | ~2GB | ✅ binlog | ✅ 支持 | 活跃 | 不支持SQL,需写代码 | ⭐⭐⭐ |
| MaterializedMySQL | 0 | - | ✅ 内置 | ✅ 支持 | 稳定版 | 需GTID,部分类型不支持,目前ck版本24.3不支持mysql8.4,最高只能mysql8.0 | ⭐⭐⭐⭐ |
| ClickHouse MySQL引擎 | 0 | - | ❌ 查询时 | ❌ 只读 | 实验性 | 性能差,仅查询时同步 | ⭐ |
| DataX | 1 | 低 | ❌ 离线 | ❌ 离线 | 活跃 | 离线同步,无实时增量 | ⭐⭐ |
方案一:go-mysql
架构:
MySQL → go-mysql → ClickHouse
✅ 优势
| 优势 | 说明 |
|---|---|
| 极轻量 | 单容器,内存占用 ~100MB |
| 原生支持 CK | 无需中间件,直接写入 ClickHouse |
| 配置简单 | 一个 YAML 配置文件即可 |
| 全量+增量 | 自动完成历史数据同步 + binlog 增量 |
| 断点续传 | 自动保存 binlog 位置 |
| 指定单表 | 精确控制同步哪些表 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 无 Exactly-Once | 只有 At-Least-Once 语义 |
| 社区较小 | 相比 Flink 社区规模小 |
| 无复杂计算 | 不支持数据转换、聚合 |
适用场景
- ✅ 单表或少量表同步(< 10张表)
- ✅ 追求轻量级部署
- ✅ 运维资源有限
- ❌ 需要复杂数据转换
方案二:SeaTunnel
架构:
MySQL → SeaTunnel Source → SeaTunnel Transform → SeaTunnel Sink → ClickHouse
✅ 优势
| 优势 | 说明 |
|---|---|
| 轻量级 | 比 Flink 轻量,单机即可运行 |
| 配置简单 | YAML 配置文件,无需写代码 |
| 多源支持 | 100+ 连接器:MySQL、ClickHouse、ES、Kafka、Hive |
| 批流一体 | 支持批量和流式同步 |
| 原生支持 CK | 直接写入 ClickHouse |
| 断点续传 | 支持检查点恢复 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 社区较新 | 2022 年进入 Apache 孵化器,成熟度不如 Flink |
| 文档不完善 | 部分场景文档缺失 |
| Bug 较多 | 生产环境需要充分测试 |
| 性能不如 Flink | 单机架构,无法水平扩展 |
适用场景
- ✅ 中小规模数据同步
- ✅ 简单的表对表同步
- ✅ 不想学习 Flink
- ❌ 超大规模数据
配置示例
yaml
# seatunnel.conf
env {
execution.parallelism = 1
job.mode = "STREAMING"
checkpoint.interval = 5000
}
source {
MySQL-CDC {
result_table_name = "source_table"
server-id = 5400
username = "root"
password = "dzh123456"
database-name = "dzh3136_go"
table-name = "addons_customer_pro_clues"
base-url = "jdbc:mysql://mysql80:3306"
}
}
sink {
Clickhouse {
host = "clickhouse:8123"
database = "dzh3136_go"
table = "addons_customer_pro_clues"
username = "default"
password = "dzh123456"
}
}
方案三:Flink CDC + Kafka(企业级)
架构:
MySQL → Flink CDC → Kafka → ClickHouse
✅ 优势
| 优势 | 说明 |
|---|---|
| 生产级稳定 | Apache 顶级项目,经过大规模验证 |
| Exactly-Once | 端到端精确一次语义,数据不丢失不重复 |
| 生态完整 | 支持 20+ 数据源 |
| 高吞吐 | 分布式架构,可水平扩展 |
| 实时计算 | 可在同步过程中进行数据转换、聚合、关联 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 组件多 | 需要 5 个容器(Flink JM/TM + Kafka + ZK) |
| 资源消耗大 | 内存占用 4GB+ |
| 学习曲线陡 | 需要了解 Flink、Kafka、状态管理 |
| 运维复杂 | 需要管理多个组件 |
| 不能直连 CK | flink-connector-clickhouse 不支持 SQL,必须通过 Kafka 中转 |
适用场景
- ✅ 大规模数据同步(TB 级)
- ✅ 需要数据转换、清洗、聚合
- ✅ 需要严格 Exactly-Once 语义
- ❌ 小规模简单同步(杀鸡用牛刀)
方案四:Flink CDC 直连(DataStream API)
架构:
MySQL → Flink CDC (DataStream) → ClickHouse
✅ 优势
| 优势 | 说明 |
|---|---|
| 减少组件 | 不需要 Kafka |
| 生产级稳定 | Flink CDC 成熟可靠 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 不支持 SQL | flink-connector-clickhouse 仅支持 DataStream API |
| 开发复杂 | 需要写 Java/Scala 代码 |
| 无法用 SQL Client | 必须打包提交作业 |
适用场景
- ✅ 有 Flink 开发能力的团队
- ❌ 想用纯 SQL 完成同步
方案五:Debezium Server
架构:
MySQL → Debezium Server → ClickHouse (HTTP)
✅ 优势
| 优势 | 说明 |
|---|---|
| 官方维护 | Red Hat 出品,质量有保证 |
| 社区活跃 | 文档完善,问题易解决 |
| 配置灵活 | 支持复杂的路由和转换 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 需要 Java | 内存占用 ~300MB |
| CK 支持有限 | 需要通过 HTTP 接口写入 |
| 配置复杂 | 需要编写配置文件 |
适用场景
- ✅ 中等规模同步(10-50张表)
- ✅ 需要官方支持和活跃社区
方案六:Canal + Adapter
架构:
MySQL → Canal Server → Canal Adapter → ClickHouse
✅ 优势
| 优势 | 说明 |
|---|---|
| 阿里出品 | 文档丰富,国内用户多 |
| 成熟稳定 | Canal Server 经过大规模验证 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| Adapter 无原生 CK | Canal Adapter 不支持 ClickHouse |
| 项目停更 | Adapter 最后更新 2022 年左右 |
| UPDATE/DELETE 语法不兼容 | ClickHouse 不支持标准 SQL UPDATE/DELETE 语法 |
| MySQL 8.4 兼容性 | 未知,可能有问题 |
⚠️ 实际遇到的问题
问题一:UPDATE/DELETE 语法不兼容
Canal Adapter RDB 模式生成标准 SQL,但 ClickHouse 使用不同的语法:
- MySQL UPDATE → ClickHouse 需要
ALTER TABLE ... UPDATE - MySQL DELETE → ClickHouse 需要
ALTER TABLE ... DELETE
结果:同步失败,报语法错误。
问题二:字段映射 NullPointerException
配置文件 mapAll 未启用时,Adapter 无法确定字段映射,导致空指针异常。
根本原因 :Canal Adapter 的 RDB 模式专为 MySQL/Oracle/PostgreSQL 等传统关系型数据库设计,无法直接用于 ClickHouse。
可行的替代方案
| 方案 | 说明 | 复杂度 |
|---|---|---|
| Canal + Kafka + 自定义消费者 | 自行转换 SQL 语法 | 高 |
| Canal + ReplacingMergeTree | 仅 INSERT,UPDATE 转 INSERT | 中 |
| 放弃 Canal | 使用 go-mysql、MaterializedMySQL 等 | 低 |
适用场景
- ✅ 已有 Canal 基础设施,且仅需 INSERT 同步
- ❌ 从零搭建不推荐
- ❌ 需要 UPDATE/DELETE 同步不推荐
方案七:ClickHouse MySQL 引擎
架构:
ClickHouse → MySQL 引擎表 → 直接查询 MySQL 数据
✅ 优势
| 优势 | 说明 |
|---|---|
| 零同步延迟 | 实时查询 MySQL,无需同步过程 |
| 配置简单 | 一条 SQL 即可创建 |
| 无额外组件 | ClickHouse 内置 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 性能差 | 每次查询都远程访问 MySQL |
| MySQL 压力 | 所有查询压力传导到 MySQL |
| 功能受限 | 不支持所有 ClickHouse 函数 |
| 仅适合小表 | 大表查询性能急剧下降 |
适用场景
- ✅ 小表实时查询(< 10万行)
- ✅ 数据迁移过渡期
- ❌ 大表分析查询
- ❌ 高并发查询
配置示例
sql
CREATE TABLE mysql_source
ENGINE = MySQL('mysql80:3306', 'dzh3136_go', 'addons_customer_pro_clues', 'root', 'dzh123456')
AS SELECT * FROM addons_customer_pro_clues;
方案八:DataX(离线同步)
架构:
MySQL → DataX → ClickHouse
✅ 优势
| 优势 | 说明 |
|---|---|
| 阿里开源 | 成熟稳定 |
| 原生支持 CK | 有 ClickHouse Writer |
| 配置简单 | JSON 配置文件 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 离线同步 | 不支持实时增量 |
| 无 binlog 解析 | 无法捕获 INSERT/UPDATE/DELETE |
| 无断点续传 | 每次全量或基于时间字段过滤 |
适用场景
- ✅ 一次性全量迁移
- ✅ 定时批量同步(T+1)
- ❌ 实时同步
方案九:clickhouse-mysql-data-reader
架构:
MySQL → clickhouse-mysql-data-reader → ClickHouse
✅ 优势
| 优势 | 说明 |
|---|---|
| Altinity 出品 | ClickHouse 核心贡献公司开发 |
| 原生支持 CK | 直接写入 ClickHouse,无需中间件 |
| 自动建表 | 自动根据 MySQL 表结构创建 ClickHouse 表 |
| 全量+增量 | 支持全量迁移和 binlog 增量同步 |
| 轻量级 | Python 实现,内存占用 ~200MB |
| 数据类型映射 | 自动转换 MySQL 到 ClickHouse 数据类型 |
❌ 劣势
| 劣势 | 说明 |
|---|---|
| 仅支持 INSERT | 不支持 UPDATE 和 DELETE 操作同步 |
| 无 DDL 同步 | 表结构变更不会自动同步 |
| 依赖复杂 | 需要安装 MySQL 客户端库 |
| 性能一般 | 相比 go-mysql 性能较低 |
适用场景
- ✅ 日志数据、事件数据等追加写入场景
- ✅ 只需要 INSERT 同步的场景
- ✅ 数据迁移(全量 + 增量)
- ❌ 需要 UPDATE/DELETE 同步
- ❌ 需要实时双向同步
配置示例
bash
# 安装
pip3 install clickhouse-mysql
# 全量 + 增量同步
clickhouse-mysql \
--src-host=127.0.0.1 \
--src-user=reader \
--src-password=qwerty \
--src-tables=dzh3136_go.addons_customer_pro_clues \
--dst-host=127.0.0.1 \
--dst-user=default \
--dst-password=dzh123456 \
--dst-database=dzh3136_go \
--dst-create-table \
--migrate-table \
--pump-data
方案十:MaterializedMySQL(ClickHouse 22.0+)
架构:
MySQL → ClickHouse MaterializedMySQL 引擎(内置复制)
使用感受
- 要求整库同步,不支持单表同步
- 编辑数据,会先插入新数据,再删除旧数据,查询太快会导致看不到最新数据
- 对比使用go-mysql,查询速度慢了三倍,100万条数据查询,同样的条件,go-mysql是200ms,MaterializedMySQL需要650ms
版本支持
MySQL 版本支持
| MySQL 版本 | 支持状态 | 说明 |
|---|---|---|
| MySQL 8.0+ | ✅ 完全支持 | 推荐,功能最完整 |
| MySQL 5.7 | ✅ 支持 | 需开启 GTID |
| MySQL 5.6 | ⚠️ 部分支持 | 需开启 GTID,部分功能受限 |
| MySQL 5.5 及以下 | ❌ 不支持 | 不支持 GTID |
| MariaDB 10.2+ | ✅ 支持 | 兼容 MySQL 协议 |
| MariaDB 10.0-10.1 | ⚠️ 部分支持 | 需开启 GTID |
ClickHouse 版本支持
| ClickHouse 版本 | 支持状态 | 说明 |
|---|---|---|
| 24.0+ | ✅ 完全支持 | 推荐,功能最完整,性能最优 |
| 23.0+ | ✅ 支持 | 稳定版本 |
| 22.0+ | ✅ 支持 | MaterializedMySQL 正式稳定 |
| 21.0+ | ⚠️ 实验性 | 功能不完整,不推荐生产使用 |
| 20.0 及以下 | ❌ 不支持 | 无 MaterializedMySQL 引擎 |
支持的操作
| 操作类型 | 支持状态 | 说明 |
|---|---|---|
| INSERT | ✅ 完全支持 | 实时同步 |
| UPDATE | ✅ 完全支持 | 转为 DELETE + INSERT |
| DELETE | ✅ 完全支持 | 实时同步 |
| CREATE TABLE | ✅ 支持 | 自动创建对应表 |
| ALTER TABLE ADD COLUMN | ✅ 支持 | 自动添加列 |
| ALTER TABLE DROP COLUMN | ⚠️ 部分支持 | 可能需要手动处理 |
| ALTER TABLE MODIFY COLUMN | ⚠️ 部分支持 | 类型变更可能失败 |
| DROP TABLE | ✅ 支持 | 自动删除对应表 |
| RENAME TABLE | ⚠️ 部分支持 | 可能需要重新创建同步 |
| TRUNCATE TABLE | ✅ 支持 | 清空表数据 |
| CREATE INDEX | ❌ 不支持 | ClickHouse 索引机制不同 |
| 外键约束 | ❌ 不支持 | ClickHouse 不支持外键 |
| 触发器 | ❌ 不支持 | ClickHouse 不支持触发器 |
| 存储过程 | ❌ 不支持 | ClickHouse 不支持存储过程 |
✅ 优势
| 优势 | 说明 | 详细说明 |
|---|---|---|
| 零组件 | ClickHouse 内置,无需部署额外服务 | 无需 Canal、Flink、Kafka 等中间件,架构最简单 |
| 零资源消耗 | 无额外 CPU、内存、磁盘消耗 | 复用 ClickHouse 资源,无独立进程 |
| 自动同步 | 自动作为 MySQL Slave 读取 Binlog | 自动建立复制连接,无需手动干预 |
| 支持完整 DML | INSERT/UPDATE/DELETE 全部支持 | UPDATE 转为 DELETE + INSERT,保证一致性 |
| 支持部分 DDL | 表结构变更自动同步 | CREATE TABLE、ADD COLUMN、DROP TABLE 自动同步 |
| 实时同步 | 毫秒级延迟 | Binlog 实时读取,延迟 < 100ms |
| 配置极简 | 一条 SQL 即可创建同步 | 无需配置文件,无需启动服务 |
| 强一致性 | 基于 MySQL 复制协议 | 保证数据不丢失、不重复 |
| 自动建表 | 自动根据 MySQL 表结构创建 ClickHouse 表 | 自动转换数据类型,自动设置排序键 |
| 断点续传 | 自动保存 Binlog 位置 | 重启后自动从上次位置继续同步 |
| 支持多表 | 可同步整个数据库或单表 | 灵活选择同步范围 |
| 支持过滤 | 可设置 WHERE 条件过滤数据 | 支持条件同步 |
❌ 劣势
| 劣势 | 说明 | 详细说明 | 影响程度 |
|---|---|---|---|
| 版本要求高 | ClickHouse 22.0+ 才稳定 | 旧版本功能不完整或存在 Bug | ⚠️ 中 |
| GTID 必需 | MySQL 必须开启 GTID | 未开启 GTID 无法使用,需修改 MySQL 配置并重启 | ⚠️ 中 |
| 部分类型不支持 | JSON、ENUM、GEOMETRY 等 | 转为 String 存储,丢失类型信息,需手动处理 | ⚠️ 中 |
| 空间类型不支持 | GEOMETRY、POINT、LINESTRING 等 | 无法同步空间数据,需其他方案 | ⚠️ 中 |
| DDL 支持有限 | 仅支持部分 DDL 操作 | MODIFY COLUMN、RENAME TABLE 可能失败,需手动处理 | ⚠️ 中 |
| 调试困难 | 同步问题排查较复杂 | 错误信息不明确,需查看 ClickHouse 日志 | ⚠️ 中 |
| 无数据转换 | 不支持数据清洗、转换 | 数据原样同步,无法在同步过程中处理数据 | ⚠️ 低 |
| 无分库分表 | 不支持分库分表同步 | 需为每个分库单独创建同步 | ⚠️ 低 |
| 单点故障 | MySQL 故障时无法同步 | MySQL 不可用时,同步停止(可设置允许查询) | ⚠️ 低 |
| 性能依赖 MySQL | 同步性能受 MySQL Binlog 影响 | MySQL 高负载时,同步延迟增加 | ⚠️ 低 |
| 无监控界面 | 无 Web 管理界面 | 需通过 SQL 或日志查看同步状态 | ⚠️ 低 |
| 无 Exactly-Once 保证 | 极端情况可能数据不一致 | 网络分区等极端情况,但概率极低 | ⚠️ 低 |
适用场景
推荐使用:
- ✅ ClickHouse 22.0+ 版本
- ✅ MySQL 5.7+ 且开启 GTID
- ✅ 追求最简架构(零组件)
- ✅ 需要实时同步(INSERT/UPDATE/DELETE)
- ✅ 数据量中小规模(< 1TB)
- ✅ 无复杂数据转换需求
- ✅ 无空间数据类型
- ✅ 追求低资源消耗
不推荐使用:
- ❌ ClickHouse 21.0 及以下版本
- ❌ MySQL 5.5 及以下版本
- ❌ MySQL 未开启 GTID 且无法重启
- ❌ 需要复杂的数据转换、清洗
- ❌ 需要空间数据类型(GEOMETRY 等)
- ❌ 分库分表场景
- ❌ 需要监控界面
- ❌ 超大规模数据(> 10TB)
Exactly-Once 语义详解
什么是数据一致性语义?
在分布式数据同步系统中,存在三种数据一致性语义:
| 语义 | 含义 | 结果 |
|---|---|---|
| At-Most-Once | 最多一次 | 数据可能丢失,但绝不重复 |
| At-Least-Once | 至少一次 | 数据绝不丢失,但可能重复 |
| Exactly-Once | 精确一次 | 数据不丢失、不重复,精确一致 |
Exactly-Once 的核心问题
问题场景:网络故障导致数据重复
时间线:
T1: MySQL 执行 INSERT (id=1, name='张三')
T2: go-mysql 读取 binlog,发送到 ClickHouse
T3: ClickHouse 写入成功 ✅
T4: go-mysql 准备保存 binlog 位置
T5: 网络故障 ❌(go-mysql 崩溃,未保存位置)
T6: go-mysql 重启,从上次保存的位置重新读取
T7: 重新读取到 T1 的 binlog
T8: ClickHouse 再次写入 (id=1, name='张三') ❌ 数据重复!
结果:ClickHouse 中有 2 条相同的记录
At-Least-Once 的实际影响
场景一:INSERT 操作重复
sql
-- MySQL
INSERT INTO orders (id, amount) VALUES (1001, 99.9);
-- ClickHouse(重复写入后)
SELECT * FROM orders WHERE id = 1001;
┌──id─┬─amount─┐
│ 1001 │ 99.9 │ ← 第1次写入
│ 1001 │ 99.9 │ ← 第2次写入(重复)
└─────┴────────┘
-- 统计错误
SELECT COUNT(*) FROM orders WHERE id = 1001; -- 结果: 2(应该是1)
SELECT SUM(amount) FROM orders; -- 结果: 199.8(应该是99.9)
场景二:UPDATE 操作重复
sql
-- MySQL
UPDATE orders SET amount = 199.9 WHERE id = 1001;
-- ClickHouse(重复写入后)
-- 如果使用 ReplacingMergeTree,会自动去重
-- 如果使用普通表引擎,会产生多条版本记录
场景三:DELETE 操作重复
sql
-- MySQL
DELETE FROM orders WHERE id = 1001;
-- ClickHouse(重复写入后)
-- 第1次删除成功
-- 第2次删除无影响(记录已不存在)
-- DELETE 重复通常无影响
为什么 go-mysql 无法实现 Exactly-Once?
技术原因:
-
单机架构
- go-mysql 是单进程程序,无法分布式协调
- 没有 Checkpoint 机制
-
无状态管理
- 只保存 binlog 位置,不保存数据状态
- 无法回滚已写入的数据
-
无事务支持
- ClickHouse 写入和 binlog 位置保存不在同一事务
- 无法保证原子性
对比 Flink CDC + Kafka:
Flink CDC 的 Exactly-Once 实现:
1. Checkpoint 机制
- 定期保存全局状态快照
- 包含:binlog 位置 + 已发送到 Kafka 的数据
2. 两阶段提交(2PC)
- 预提交:写入 Kafka,但不提交
- 正式提交:Checkpoint 成功后,才真正提交
3. Kafka 事务
- Kafka 支持事务写入
- 消费者只能看到已提交的消息
4. 幂等性
- ClickHouse 使用 ReplacingMergeTree
- 重复数据自动去重
Exactly-Once 的代价
| 代价 | 说明 |
|---|---|
| 架构复杂 | 需要 Kafka + Flink + Checkpoint 存储 |
| 资源消耗 | 内存占用 4GB+,CPU 多核 |
| 延迟增加 | Checkpoint 间隔(通常 5-10秒) |
| 运维复杂 | 需要管理多个组件 |
实际场景分析
场景一:日志数据同步
特点:
- 仅 INSERT 操作
- 数据量大
- 允许少量重复
- 统计误差可接受(0.01% 重复率)
推荐:go-mysql(At-Least-Once 足够)
场景二:订单数据同步
特点:
- INSERT + UPDATE 操作
- 数据准确性要求高
- 金额统计不能有误差
推荐:
- 方案A:Flink CDC + Kafka(Exactly-Once)
- 方案B:go-mysql + ReplacingMergeTree(自动去重)
场景三:用户数据同步
特点:
- INSERT + UPDATE + DELETE
- 数据量中等
- 需要精确一致性
推荐:MaterializedMySQL(强一致)
解决 At-Least-Once 问题的方法
方法一:使用 ReplacingMergeTree 引擎
sql
-- 创建表(使用 ReplacingMergeTree)
CREATE TABLE orders
(
id UInt32,
amount Decimal(10, 2),
version UInt32 -- 版本号字段
)
ENGINE = ReplacingMergeTree(version) -- 按 version 去重
ORDER BY id;
-- 即使重复写入,查询时也会自动去重
SELECT * FROM orders FINAL WHERE id = 1001; -- 只返回最新版本
原理:
- ReplacingMergeTree 自动保留 version 最大的记录
- 重复数据会被合并删除
- 查询时使用
FINAL关键字强制合并
方法二:使用 CollapsingMergeTree 引擎
sql
-- 创建表(使用 CollapsingMergeTree)
CREATE TABLE orders
(
id UInt32,
amount Decimal(10, 2),
sign Int8 -- 符号列:1 表示插入,-1 表示取消
)
ENGINE = CollapsingMergeTree(sign)
ORDER BY id;
-- 写入数据
INSERT INTO orders VALUES (1001, 99.9, 1); -- 原始数据
INSERT INTO orders VALUES (1001, 99.9, -1); -- 取消(重复数据)
INSERT INTO orders VALUES (1001, 99.9, 1); -- 正确数据
-- 查询时自动折叠
SELECT * FROM orders FINAL WHERE id = 1001; -- 只返回 sign=1 的记录
方法三:定期去重任务
sql
-- 定期执行去重
INSERT INTO orders_clean
SELECT
id,
amount,
MAX(version) as version
FROM orders
GROUP BY id, amount;
-- 替换原表
RENAME TABLE orders TO orders_old, orders_clean TO orders;
DROP TABLE orders_old;
方法四:使用唯一索引(ClickHouse 24.1+)
sql
-- ClickHouse 24.1+ 支持唯一索引
CREATE TABLE orders
(
id UInt32,
amount Decimal(10, 2)
)
ENGINE = MergeTree
ORDER BY id
SETTINGS index_granularity = 8192;
-- 设置唯一约束
ALTER TABLE orders ADD CONSTRAINT unique_id UNIQUE (id);
各方案一致性对比
| 方案 | 一致性语义 | 重复风险 | 解决方法 |
|---|---|---|---|
| go-mysql | At-Least-Once | 中 | ReplacingMergeTree |
| SeaTunnel | At-Least-Once | 中 | ReplacingMergeTree |
| Flink CDC + Kafka | Exactly-Once | 无 | 内置事务 |
| Debezium | At-Least-Once | 中 | ReplacingMergeTree |
| MaterializedMySQL | 强一致 | 无 | 内置复制协议 |
| Canal | At-Least-Once | 高 | 不推荐 |
总结
Exactly-Once 是否必需?
| 场景 | 是否必需 | 推荐方案 |
|---|---|---|
| 日志数据 | ❌ 不必需 | go-mysql |
| 统计报表(允许误差) | ❌ 不必需 | go-mysql |
| 订单金额(不允许误差) | ✅ 必需 | Flink CDC 或 MaterializedMySQL |
| 用户数据(精确一致性) | ✅ 必需 | MaterializedMySQL |
| 金融交易(严格一致性) | ✅ 必需 | Flink CDC + Kafka |
实践经验:
-
90% 场景不需要 Exactly-Once
- 使用 ReplacingMergeTree 即可解决重复问题
- go-mysql + ReplacingMergeTree 是最佳组合
-
10% 场景需要 Exactly-Once
- 金融、订单等关键数据
- 使用 Flink CDC + Kafka 或 MaterializedMySQL
-
At-Least-Once + 去重引擎 = 实际 Exactly-Once
- 成本远低于真正的 Exactly-Once
- 性能更好,架构更简单
综合对比表
| 维度 | go-mysql | SeaTunnel | Flink CDC+Kafka | Debezium | clickhouse-mysql-reader | Canal | MaterializedMySQL | DataX |
|---|---|---|---|---|---|---|---|---|
| 实时性 | 毫秒级 | 秒级 | 毫秒级 | 毫秒级 | 毫秒级 | 毫秒级 | 毫秒级 | 离线 |
| 直连CK | ✅ | ✅ | ⚠️ Kafka | ⚠️ HTTP | ✅ | ❌ | ✅ 内置 | ✅ |
| INSERT | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| UPDATE | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| DELETE | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| DDL同步 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ⚠️ 部分 | ❌ |
| 资源消耗 | 极低 | 低 | 高 | 中 | 低 | 中 | 极低 | 低 |
| 配置复杂度 | 低 | 低 | 高 | 中 | 中 | 中 | 极低 | 低 |
| 维护状态 | 活跃 | 活跃 | 活跃 | 活跃 | 维护中 | 停更 | 稳定 | 活跃 |
| 数据一致性 | At-least-once | At-least-once | Exactly-once | At-least-once | At-least-once | At-least-once | 强一致 | - |
| 全量+增量 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| 自动建表 | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ |
| 适用数据量 | 小-中 | 中 | 大 | 中 | 小-中 | 中 | 小-中 | - |
推荐建议
根据场景选择
🏆 推荐方案排序
-
go-mysql(推荐 ⭐⭐⭐⭐⭐)
- 原因:极轻量、原生支持 CK、配置简单、支持 UPDATE/DELETE
- 适合:单表/少量表同步,追求轻量部署
-
MaterializedMySQL(推荐 ⭐⭐⭐⭐)
- 原因:零组件、内置同步、支持完整 DML、配置极简
- 适合:ClickHouse 22.0+、追求最简架构
-
SeaTunnel(推荐 ⭐⭐⭐⭐)
- 原因:配置简单、支持多源、批流一体
- 适合:中等规模同步,不想学 Flink
-
Flink CDC + Kafka(推荐 ⭐⭐⭐⭐)
- 原因:生产级稳定、Exactly-Once
- 适合:大规模数据、需要严格一致性
-
clickhouse-mysql-data-reader(推荐 ⭐⭐⭐)
- 原因:原生支持 CK、自动建表、Altinity 出品
- 适合:仅 INSERT 场景、日志数据同步
-
Debezium Server(推荐 ⭐⭐⭐)
- 原因:官方维护、社区活跃
- 适合:需要官方支持
-
Flink CDC 直连(推荐 ⭐⭐⭐)
- 原因:生产级稳定、减少 Kafka 组件
- 适合:有 Flink 开发能力的团队
- 缺点:不支持 SQL,需写 Java/Scala 代码
-
Canal + Adapter(不推荐 ⭐⭐)
- 原因:Adapter 停更、UPDATE/DELETE 语法不兼容
- 适合:已有 Canal 基础设施且仅需 INSERT 同步
-
DataX(不推荐实时场景 ⭐⭐)
- 原因:不支持实时增量
- 适合:离线批量同步、一次性数据迁移
-
ClickHouse MySQL 引擎(不推荐 ⭐)
- 原因:性能差、仅查询时同步、只读
- 适合:小表实时查询(< 10万行)、数据迁移过渡期
快速决策树
需要实时同步?
├── 否 → DataX(离线批量)
└── 是 → ClickHouse 版本?
├── 22.0+ 且 MySQL 开启 GTID → MaterializedMySQL ✅(最简单)
└── 其他 → 需要同步多少张表?
├── 1-10 张 → go-mysql ✅
├── 10-50 张 → SeaTunnel ✅
└── 50+ 张 → Flink CDC + Kafka ✅
需要 UPDATE/DELETE 同步?
├── 是 → go-mysql / SeaTunnel / Flink CDC / MaterializedMySQL ✅
└── 否(仅 INSERT)→ clickhouse-mysql-data-reader ✅
需要 Exactly-Once?
├── 是 → Flink CDC + Kafka ✅
└── 否 → go-mysql / SeaTunnel / MaterializedMySQL
资源有限(< 2GB 内存)?
├── 是 → go-mysql / MaterializedMySQL ✅
└── 否 → SeaTunnel / Flink CDC
需要复杂数据转换?
├── 是 → Flink CDC + Kafka ✅
└── 否 → go-mysql / SeaTunnel / MaterializedMySQL
追求最简架构(零组件)?
├── 是 → MaterializedMySQL ✅
└── 否 → go-mysql
资源消耗对比
| 方案 | CPU | 内存 | 磁盘 | 部署时间 |
|---|---|---|---|---|
| MaterializedMySQL | 0 | 0 | 0 | 5分钟 |
| go-mysql | 0.5核 | 100MB | 1GB | 10分钟 |
| clickhouse-mysql-data-reader | 0.5核 | 200MB | 2GB | 20分钟 |
| SeaTunnel | 1核 | 1-2GB | 5GB | 20分钟 |
| Debezium Server | 1核 | 300MB | 5GB | 30分钟 |
| Flink CDC 直连 | 2核 | 2GB | 10GB | 1小时 |
| Canal + Adapter | 1核 | 1GB | 5GB | 1小时 |
| Flink CDC + Kafka | 4核+ | 4GB+ | 50GB+ | 2小时+ |
| DataX | 1核 | 500MB | 1GB | 15分钟 |
| ClickHouse MySQL 引擎 | 0 | 0 | 0 | 1分钟 |
总结
最终推荐
追求最简架构(零组件):
👉 强烈推荐 MaterializedMySQL
理由:
- ✅ 零组件,ClickHouse 内置
- ✅ 一条 SQL 完成同步
- ✅ 支持 INSERT/UPDATE/DELETE
- ✅ 实时同步,毫秒级延迟
- ⚠️ 要求 ClickHouse 22.0+ 且 MySQL 开启 GTID
对于单表/少量表同步场景:
👉 强烈推荐 go-mysql
理由:
- ✅ 最轻量(1个容器,~100MB)
- ✅ 原生支持 ClickHouse
- ✅ 配置最简单
- ✅ 全量+增量自动同步
- ✅ 支持 UPDATE/DELETE
- ✅ 断点续传
对于仅 INSERT 场景(日志、事件数据):
👉 推荐 clickhouse-mysql-data-reader
理由:
- ✅ Altinity 出品(ClickHouse 核心贡献公司)
- ✅ 原生支持 ClickHouse
- ✅ 自动建表
- ✅ 全量+增量同步
- ❌ 不支持 UPDATE/DELETE
对于中等规模同步:
👉 推荐 SeaTunnel
理由:
- ✅ 配置简单(YAML)
- ✅ 支持多源多目标
- ✅ 批流一体
- ✅ 支持 UPDATE/DELETE
对于大规模/企业级场景:
👉 推荐 Flink CDC + Kafka
理由:
- ✅ 生产级稳定
- ✅ Exactly-Once 语义
- ✅ 可水平扩展
- ✅ 支持复杂数据转换
方案选择速查表
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 追求最简架构 | MaterializedMySQL | 零组件,一条 SQL |
| 单表同步 | go-mysql | 极轻量,配置简单 |
| 仅 INSERT 场景 | clickhouse-mysql-data-reader | 自动建表,原生支持 |
| 中等规模 | SeaTunnel | 多源支持,批流一体 |
| 大规模/企业级 | Flink CDC + Kafka | Exactly-Once,可扩展 |
| 离线批量 | DataX | 成熟稳定 |
| 已有 Canal | Canal | 复用现有设施 |
关键决策因素
| 因素 | 选择 |
|---|---|
| ClickHouse 22.0+ 且 MySQL GTID 开启 | 优先 MaterializedMySQL |
| 仅 INSERT 操作 | clickhouse-mysql-data-reader 或 go-mysql |
| 需要 UPDATE/DELETE | go-mysql / SeaTunnel / Flink CDC / MaterializedMySQL |
| 需要 Exactly-Once | Flink CDC + Kafka |
| 资源有限(< 2GB) | go-mysql / MaterializedMySQL |
| 需要数据转换 | Flink CDC + Kafka |