很多任务慢,根子在 Reader 端:源库查询没走索引、一次性拉太多、或者没分片。Reader 端的优化,本质是「让源库舒服地吐数据」。
DataX 的查询最终是一条 SQL。如果 where 条件没命中索引,源库会全表扫描,Reader 自然慢,还会长时间锁表。我们写查询前必看执行计划,确认过滤列有索引。对于按时间增量的场景,created_at 上的索引是标配。
-- 在源库确认查询走了索引 EXPLAIN SELECT id, name FROM user WHERE created_at > '2024-01-01'; -- 若 type=ALL 则为全表扫描,需要给 created_at 加索引
splitPk 把大表切成多段并发(3.1)。fetchSize 控制每次 JDBC 拉取的批大小。两者配合:分片让多任务并行,fetchSize 让单任务少往返。我们一般 splitPk 用自增主键,fetchSize 设 2000 左右,再根据内存微调。
{ "reader": { "name": "mysqlreader", "parameter": { "connection": [{ "jdbcUrl": ["jdbc:mysql://h:3306/d"], "table": ["orders"], "splitPk": "id" }], "column": ["id", "user_id", "amount"], "where": "created_at >= '2024-01-01'", "fetchSize": 2000 } } }

能用 querySql 在源库完成过滤、简单聚合的,绝不全拉到 DataX 再处理。源库有索引、有统计信息,执行效率远高于 DataX 的单线程变换。这是我们反复强调的「下推原则」在性能上的回报。
splitPk 列若分布不均(比如大量历史数据 id 集中在某段),分片会倾斜,有的 Task 跑很久、有的早早结束。遇到这种情况,我们换更均匀的分片键,或手动指定 split 的边界值。
DataX 的查询最终是一条 SQL,where 条件没命中索引就会全表扫描,Reader 自然慢,还会长时间锁表影响线上。我们写查询前必看执行计划,确认过滤列有索引,尤其按时间增量的场景,created_at 上的索引是标配。
| 检查项 | 不满足的后果 |
|---|---|
| where 走索引 | 全表扫描拖慢 |
| splitPk 均匀 | 分片倾斜 |
| fetchSize 合理 | 往返多或内存涨 |
我们曾因漏建索引,一条增量任务把源库 CPU 打满,连带影响线上交易。此后所有同步查询上线前都要过索引评审。
生产环境的稳定性,常常取决于部署习惯而非某个高级特性。
Channel 是有界缓冲,填满即触发反压保护内存。
权限收得越紧,凭据泄露的爆炸半径越小。
自定义插件最容易踩的坑是依赖冲突,provided 范围能治本。
writeMode 必须和数据更新语义对齐,不能凭感觉选。
把转换逻辑下推到源库,通常比在 DataX 内部处理更高效。
一张参数与吞吐的经验曲线,比任何通用公式都贴近你的环境。
调优的本质是在 CPU、网络带宽和磁盘 IO 之间找平衡点,不是堆资源。
任务的读写速率差,比绝对速率更能说明瓶颈在哪一段。
退出码接进调度系统,才能让失败在半夜被及时发现。
任务可重跑幂等,是生产上线前的硬指标。
脏数据阈值设得太高,会掩盖源端的数据质量问题,反而埋雷。
Reader 和 Writer 互不知晓,正是插件能独立扩展的原因。
querySql 与 column 二选一,混用会直接报错。
源库索引评审应作为同步查询上线的前置环节。
JVM 堆要给 Channel 缓冲留足空间,否则 GC 频繁拖慢吞吐。
对象存储比 FTP 更适合做跨机房中转,因为它支持断点和内网加速。
监控指标和 DataX 日志交叉看,能锁定九成瓶颈。
机器核数、内存、带宽三者共同决定 channel 的甜点值。
星型拓扑把 N 乘 M 的对接降到 N 加 M,变更成本随之下降。
channel 数超过源端连接承受能力时,瓶颈会从 DataX 转移到数据库。
数据湖贴源层保留原始形态,方便后续 schema 演化。
eswriter 的批量 bulk 写入,比逐条插入快一个数量级。
Redis 同步重跑要防重复 key,靠固定模板才能幂等。
preSql 里带 truncate 的任务,上线前必须二次确认目标表名。
Reader 端优化集中在「少读无用数据、并行读、批量读」。对关系型源,这几个手段往往比加 channel 更有效。
{ "job": { "content": [ { "reader": { "name": "mysqlreader", "parameter": { "username": "etl", "password": "secret", "connection": [ { "jdbcUrl": ["jdbc:mysql://db:3306/order"], "table": ["t_order"], "splitPk": "id" } ], "column": ["id","user_id","amount","create_time"], "where": "create_time >= '2024-01-01'", "fetchSize": 2000, "querySql": "SELECT id,user_id,amount,create_time FROM t_order WHERE create_time >= '2024-01-01' AND id >= ${start} AND id < ${end}" } }, "writer": { "name": "hdfswriter", "parameter": { "defaultFS": "hdfs://ns", "path": "/data/order", "fileType": "text", "column": [ {"name":"id","type":"bigint"}, {"name":"user_id","type":"bigint"}, {"name":"amount","type":"double"}, {"name":"create_time","type":"string"} ] } } } ], "setting": { "speed": { "channel": 10 } } } }
# 在数据库侧确认抽取 SQL 走了索引,避免全表扫把源端拖死 mysql -u etl -p demo -e "EXPLAIN SELECT id FROM t_order WHERE create_time>='2024-01-01';"
column 只选必要列、where 尽早过滤,降 IO 与序列化开销;splitPk 让框架按主键范围分片并行;fetchSize 调大减少 JDBC 往返;where / splitPk 命中索引,否则单分片查询就全表扫,并发反而放大源端压力。对超大类表,可先用数据库分区裁剪(where 命中分区键),再配合 splitPk,把「扫描量」和「并发度」同时压下来。
| 手段 | 收益 | 前提 |
|---|---|---|
| 精减 column | 降 IO | 明确所需列 |
| where 过滤 | 降扫描 | 有索引列 |
| splitPk | 提并发 | 整型主键 |
| fetchSize | 降往返 | 内存允许 |
💡 关键直觉:Reader 优化的第一性原则是「让源端尽量少干活」。任何能让数据库少扫一行、少传一列的手段,都会线性反映到同步速度上。
⚠️ 常见坑:splitPk 选了无索引的列,框架的每个分片查询都全表扫,channel 越大源端越惨;务必先用 EXPLAIN 确认分片查询走索引再上并发。