MySQL导出需显式指定--default-character-set=utf8mb4并用--skip-extended-insert,ClickHouse表须显式声明Nullable类型,NULL值替换为\N,优先用clickhouse-client管道导入CSV/TSV格式数据。

MySQL数据导出时如何避免字符集和NULL值导致同步失败
直接用 mysqldump 导出再导入 ClickHouse,大概率在中文字段或含 NULL 的列上翻车。ClickHouse 对 NULL 处理严格(尤其非 Nullable 类型),且默认不兼容 MySQL 的 utf8mb4 编码。
- 导出必须显式指定字符集:
mysqldump --default-character-set=utf8mb4 -u user -p db table > data.sql - 导出时禁用扩展插入(避免 ClickHouse 解析失败):
--skip-extended-insert - 对可能为
NULL的字段,在 ClickHouse 表定义中显式声明为Nullable(String)或对应类型,不要依赖默认行为 - 导出后建议用
sed或 Python 预处理:把NULL替换为\N(ClickHouse 的 NULL 字面量),并删掉CREATE TABLE和INSERT INTO语句头尾——ClickHouse 原生支持INSERT FORMAT CSV/TSV,比 SQL 插入快且稳定
ClickHouse 接收数据时该用 INSERT 还是 clickhouse-client 的 batch 模式
用 INSERT INTO ... VALUES 单条或小批量插入,吞吐极低,且容易触发 Too many simultaneous queries 错误;而 clickhouse-client --query="INSERT ..." 管道输入又难控制格式。
- 推荐走
clickhouse-client --format=CSV --query="INSERT INTO table FORMAT CSV",配合mysqldump --tab输出的纯数据文件(无 SQL 头尾) - 确保 CSV 字段顺序、类型、NULL 标记(
\N)与 ClickHouse 表结构完全一致,否则报Cannot parse input - 大表同步前先关掉
replicated_merge_tree的后台合并:SET replicated_deduplication_window=0,避免写入期间触发重复校验拖慢速度 - 单次导入超过 100 万行,建议分批次(如每 50000 行一个文件),用
xargs -P 4并行导入,但注意 ClickHouse 默认max_threads=16,别盲目开太高
如何用 Python 脚本做增量同步而非全量重刷
全量同步只能用于首次初始化;日常更新必须靠增量。MySQL 没有原生 CDC,得靠 binlog + mysql-binlog-connector-python 或更稳的 maxwell / debezium 中间件。但若只跑轻量脚本,可退而求其次用时间戳 + 自增主键双保险。
- MySQL 表必须有
updated_atDATETIME 字段(自动更新)或idBIGINT 自增主键,否则无法判断增量范围 - Python 脚本每次运行前,先查 ClickHouse 当前最大
updated_at或id:SELECT max(updated_at) FROM target_table - 再从 MySQL 查出新增/更新数据:
SELECT * FROM source_table WHERE updated_at > '{last_max}',结果用pandas.to_csv(index=False, na_rep=r'\N')输出为 TSV - 关键点:ClickHouse 的
INSERT不支持ON DUPLICATE KEY UPDATE,所以得先DELETE WHERE再INSERT,或者用ReplacingMergeTree引擎 +version字段做最终去重
同步脚本里怎么处理 MySQL TEXT 和 ClickHouse String 的长度差异
MySQL 的 TEXT 实际可存 64KB,而 ClickHouse 的 String 理论无上限,但真实场景中,超长文本(如日志、HTML 片段)会导致内存暴涨甚至 OOM —— 尤其在 GROUP BY 或 JOIN 时。
立即学习“Python免费学习笔记(深入)”;
- 不要无脑映射
TEXT → String;对明确超长字段(如content,description),改用LowCardinality(String)(仅当去重后唯一值 FixedString(8192) 截断 - Python 导出前加校验:
if len(row['content']) > 8000: row['content'] = row['content'][:7999] + '…' - ClickHouse 表建表语句里,对这类字段加注释说明截断逻辑,避免下游误用
- 如果业务真需要完整长文本,单独建一张
xxx_content表,用UUID关联,主表只存摘要
实际跑通的关键不在“怎么连”,而在字段类型映射是否闭环、NULL 和编码是否对齐、增量逻辑有没有漏数据。很多脚本本地测试 OK,一上生产就卡在某张表的某个字段上——往往就是 TEXT 字段没截断,或者 updated_at 用了 TIMESTAMP 但 MySQL 时区设成了 +08:00,而 ClickHouse 是 UTC。


















