Oracle到MySQL数据同步实战:DataX并发通道与分片配置详解
在一家同时跑着 Oracle 和 MySQL 的公司里做数据平台最常遇到的场景就是核心业务系统都在 Oracle 上但后建的 BI 报表、经营分析、数据中台只认 MySQL。两边要定期同步数据这活儿听起来简单真做起来却有一堆讲究。我最开始是让开发写存储过程通过 dblink 往 MySQL 插数。结果每次同步都心惊胆战源库 CPU 被拉高目标库写入一慢就堵事务不同数据库之间的类型还得手工转换字段一多配置就乱。后来换了 DataX再把同步任务统一放到 DataX-Web 上调度管理才算是把这块彻底理顺。这篇就完整记录一次 Oracle 到 MySQL 的实战配置。重点不是教你怎么点按钮而是把 DataX 里最核心的四个概念——并发通道Channel、分片字段splitPk、TaskGroup任务组、Task子任务——用一条真实同步配置串起来讲明白。适合刚开始接触 DataX、想把批量同步任务做得又快又稳的读者参考。1. 先说清楚业务场景Oracle 主库为什么非得往 MySQL 同步很多公司并不是一开始就想搞两套数据库而是业务发展过程中自然长成的。我们这边的情况是订单、客户、商品这些核心数据都在 Oracle 里ERP、CRM、WMS 各种系统都依赖它不能随便动。但近几年上的数据分析平台、可视化报表、甚至是一些外包团队开发的新系统都是基于 MySQL 的。新系统不可能直连 Oracle 生产库只能靠周期性同步拿数据。1.1 手工脚本同步的痛点在哪里最早我们用的是 dblink 加存储过程每天晚上跑批。这种方式在小数据量下没什么问题但数据量一旦上来问题就非常明显。一个是数据库负载不可控。dblink 方式本质上是把源库当成了一个普通客户端存储过程里如果写了全表扫描逻辑Oracle 的 CPU 和 IO 会瞬间飙高影响白天还在跑的业务。另一个是数据一致性不好保证。跑批过程中网络抖动、目标表锁冲突、字段超长任何一个环节出错整个存储过程就中断了第二天发现报表数据是残缺的还得人工对账重跑。更麻烦的是类型转换。Oracle 的 NUMBER、VARCHAR2、DATE 转到 MySQL 的 int、varchar、datetime不是简单的一一对应。一开始我们靠开发在 SQL 里手工写 TO_CHAR、TO_NUMBER字段少还能应付字段上百个以后维护成本高到让人崩溃。1.2 为什么挑 DataX 而不是其他同步工具当时也考察过其他方案比如基于日志的 CDC 工具、商业 ETL 工具甚至直接用 Kettle 拖拽。最后选 DataX 主要是看中几点。DataX 是阿里开源的异构数据源同步框架插件化设计Oracle 和 MySQL 的 reader、writer 都是现成的改改 JSON 就能用不需要写一行业务代码。它对源端是普通的 JDBC 查询方式不依赖数据库日志也就不会对 Oracle 的归档模式、日志解析权限提出额外要求这一点在生产库上很重要。还有一个原因是它的并发模型设计得比较直观一个 channel 就是一条数据通道想提速加通道数就行出问题容易排查。当时也担心过 DataX 社区维护节奏的问题但这么多年用下来发现同步任务这种场景对版本迭代并不敏感稳定才是第一位的所以一直用到现在。1.3 DataX-Web 在 DataX 之上补了什么单纯用 DataX 命令行走任务问题在于没有统一的管理界面。任务分布在各个服务器上跑没跑成功全靠人肉盯日志调度只能靠 crontab谁改过任务配置也无从追溯。DataX-Web 是一个基于 DataX 的可视化调度管理平台。它把 DataX 的 Job JSON 封装成了任务模板再通过任务绑定执行器的方式把任务分发到指定机器上执行。页面里能看到每个任务的历史执行记录、日志、运行状态还能配 cron 表达式做周期性调度。对我来说它最大的价值不是省了敲命令那几秒钟而是让整个同步体系从“脚本散落”变成了“平台统一”。2. 环境准备与第一个同步任务的完整发布流程现在开始进入实操。假设你已经有两套环境Oracle 19c 在 192.168.10.20MySQL 8.0 在 192.168.10.30网络互通内网同步。下面这套流程我按自己的部署习惯写版本不同界面可能有细微差别但核心思路一致。2.1 环境安装两台机器需要准备什么DataX 本体和 DataX-Web 可以部署在同一台机器上也可以分开。我建议把 DataX 装到离源库网络比较近的一台单独服务器上别和业务应用混跑同步任务对带宽和 IO 的占用不小。安装 DataX 本体很简单下载 DataX 安装包解压后目录里有 bin、plugin、lib 等几个子目录。核心的可执行脚本在bin/datax.py。我们需要确认 Python 环境可用且本机已经装了 JDK 8 及以上版本。DataX 本身是 Java 写的JDK 版本太老或太新都可能踩坑。DataX-Web 我用的是 GitHub 上开源的版本它分为两个部分前端 Web 页面和执行器executor。前端负责配置任务、查看日志执行器负责真正拉起 DataX 进程。部署时先把 DataX-Web 的后端服务启动起来再配置执行器地址让前端能连接到执行器机器。有一点容易被忽略DataX-Web 的调度中心和执行器最好都配置了时区否则定时任务的执行时间和服务器本地时间对不上。我们线上就出过一次任务提前一小时跑的故障查了半天发现是容器时区默认 UTC 导致的。2.2 DataX-Web 里创建一个同步任务的入口在 DataX-Web 页面上正常流程是先创建项目一般一个项目对应一个业务线。进入“任务管理”模块创建任务模板把 DataX 的 JSON 配置粘贴进去。创建任务选择刚才的模板绑定执行器和调度表达式。如果想立即验证数据可以手动触发运行先不配调度。这里要分清楚“任务模板”和“任务”的关系。模板是静态配置相当于一类任务的公共定义任务才是调度实体绑定执行器后才有实际的运行记录。同一个模板可以创建多个任务比如不同日期的增量同步通过任务参数区分。2.3 一个可直接复制的 Oracle→MySQL 任务 JSON下面是核心部分先看一个完整的、可用的 JSON 配置。{ job: { setting: { speed: { channel: 4, byte: -1, record: -1 }, errorLimit: { record: 0, percentage: 0.02 } }, content: [ { reader: { name: oraclereader, parameter: { username: report_ro, password: your_password, connection: [ { table: [orders], jdbcUrl: [jdbc:oracle:thin://192.168.10.20:1521/orcl] } ], column: [ ID, ORDER_NO, AMOUNT, CREATE_TIME ], splitPk: ID, where: CREATE_TIME to_date(2024-08-01, yyyy-mm-dd) } }, writer: { name: mysqlwriter, parameter: { username: report, password: your_password, connection: [ { table: [orders], jdbcUrl: [jdbc:mysql://192.168.10.30:3306/report_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghairewriteBatchedStatementstrue] } ], column: [ ID, ORDER_NO, AMOUNT, CREATE_TIME ], preSql: [truncate table orders], writeMode: insert, batchSize: 2048 } } } ] } }这个配置的含义很直白reader 从 Oracle 的 orders 表里读 ID、ORDER_NO、AMOUNT、CREATE_TIME 四列只取 2024-08-01 之后的数据writer 把数据写入 MySQL 的 orders 表。写入前先清空目标表适合全量刷新场景。重点先看两个参数splitPk和speed.channel。这两项直接关系到并行度后面会专门展开。byte和record都设成 -1表示不限流不走字节数或记录数限制优先保证速度。如果担心源库压力可以给 byte 设一个值比如 10MB/sDataX 会按这个速度限流。2.4 为什么 Oracle Reader 要加 where 条件很多新手做全量同步时会在 Oracle 源端直接整表查询。表小的时候无所谓表一大就很伤DataX 虽然会做并发分片但查询条件没有下推到数据库Oracle 可能需要做全表扫描共享池和 IO 都会被打满。所以我习惯在 where 里先做粗过滤把需要的日期范围或状态范围写清楚。这既能让 Oracle 走索引也能减少网络传输的数据量。如果表有主键或者时间索引这个 where 条件的收益非常明显。3. 并发通道、分片字段、TaskGroup、Task 到底是怎么配合的这是整篇的核心也是很多人在 DataX 文档里看半天没看明白的地方。我用大白话把这四个概念拆开讲。3.1 一个 Job 是如何被拆成多个 Task 的DataX 里一个同步任务整体称为一个 Job也就是你在 DataX-Web 里配置的那份 JSON。Job 在运行时会经历两个阶段拆分split阶段和执行阶段。在拆分阶段DataX 会调用 reader 的 split 方法把整体的数据读取任务切成若干份每一份就是一个 Task。Task 是最小的执行单元它负责读取一段数据并写入目标端内部是单线程的。如果你不配置任何分片逻辑那 Job 只有一个 Task全表数据由这一个 Task 串行搬运速度自然上不去。这个拆分动作和两个因素有关一个是并发通道数一个是分片字段。它们共同决定了 Task 的数量和边界。3.2 splitPk 分片字段是怎么切数据分片的splitPk指定一个字段DataX 会用它把查询结果切分成多个区间。比如 orders 表的主键 ID 是从 1 到 500 万的连续自增整数channel 配成 4DataX 就会把数据大致均分成四个区间例如 1~125 万、125 万~250 万、250 万~375 万、375 万~500 万生成四个 Task。实际执行时每个 Task 会带着一段区间条件去查询 Oracle相当于在 reader 的 SQL 上自动追加了类似WHERE ID ? AND ID ?的条件。这样每个分片走主键或索引的范围扫描对源库更友好数据也能并行读取。反过来如果你不配置 splitPk哪怕 channel 配了 8DataX 也只能生成一个 Task。因为 reader 没法把数据切成多份并发配置就形同虚设。这也是我见过最多的“配了并发没提速”的原因。3.3 channel 并发通道控制的到底是什么channel 中文叫通道或者管道可以理解成一个并发线程。每个 channel 内部维护着 reader 和 writer 两端的连接一个 channel 同时只能执行一个 Task。你在配置里把speed.channel从 1 改成 4本质上是告诉 DataX同一时刻最多可以有 4 个 Task 并行执行。这 4 个 Task 会各自从 Oracle 读数据并往 MySQL 写数据。所以通道数越高同一时刻在跑的数据库连接数、网络连接数、内存占用也会跟着升高。3.4 TaskGroup 任务组是怎么把 Task 组织起来的TaskGroup 是很多人最容易混淆的概念。官方文档和各类文章里一会儿说 Task 一会儿说 TaskGroup搞得人云里雾里。在 DataX 的运行框架里Job 拆分出来的 N 个 Task 并不会一下子全部交给 N 个线程去跑而是先被分到若干个 TaskGroup 中。TaskGroup 相当于一个任务容器或者调度组组内维护着自己的 channel负责从中取出 Task 并分发给线程执行。你可以这样理解Task 是“干活的单元”channel 是“干活的人”TaskGroup 是“干活的小组”。公司里有个大项目被拆成了 20 个小任务不可能 20 个人同时开工人太多了协调不过来所以分成了 4 个小组每组 5 个人每个小组认领 5 个小任务谁干完谁去接下一个。TaskGroup 存在的意义正是为了解决这种调度和管理问题。如果 Task 数量很大比如几百上千个直接全部并发会产生大量连接和线程机器扛不住。通过 TaskGroup 分层调度DataX 可以有节奏地把任务分配给各个通道去消费。这里要特别提醒一下DataX-Web 页面上也有一项叫“任务组管理”但它和 DataX 内部的 TaskGroup 是两码事。DataX-Web 的任务组是用来给执行器分组的比如你有两台执行器可以分成两个任务组分别调度不同的任务DataX 的 TaskGroup 是运行时框架里的概念。别把这两者混了坑过不少人。3.5 四个概念放在同一个案例里看还是拿上面的 orders 表举例。假设 channel4splitPkID总数据约 500 万行。Job 是整个同步任务负责拆解和调度。split 阶段基于 ID 把数据切成 4 份生成 4 个 Task。运行时框架把 Task 分配给 TaskGroup 管理TaskGroup 里的 channel 按并发度去执行 Task。每个 Task 负责一段 ID 区间的数据单线程完成读取和写入。如果在日志里看到“task 0、task 1、task 2、task 3”这样的编号不用好奇那就是 4 个并行 Task。再看机器上的数据库连接数大概率也是 4 组左右的读写连接。4. 并发参数到底怎么调一次真实压测记录理论讲完必须用数据说话。下面是我环境里一次实际压测的记录供你参考调参思路不建议直接照搬数字因为不同表结构、数据库硬件、网络环境差异很大。4.1 压测环境与固定条件源端 Oracle 19c16 核 32Gorders 表约 500 万行单行平均 1KB数据总量大约 5GB。目标端 MySQL 8.08 核 16G。中间是千兆内网。分片字段统一用 ID 主键writer 端 batchSize 固定 2048不限制流量。每个并发档位我都连续跑两次取平均值避免第一次运行时有连接初始化等冷启动影响。表结构固定目标表每次都先 truncate 再写入保证口径一致。4.2 不同 channel 数下的实测对比结果如下表channel 数总耗时平均同步速度源库 CPU 峰值表现评述118 分 30 秒约 4.6 MB/s15%稳定但太慢单通道是保底方案210 分 05 秒约 8.5 MB/s28%速度明显提升源库压力可控46 分 12 秒约 13.7 MB/s55%速度与压力比较平衡84 分 08 秒约 20.1 MB/s80%速度最快源库负载已偏高165 分 40 秒约 15.2 MB/s95%速度反而下降源库成了瓶颈这个结果很典型通道数从 1 加到 8速度接近线性提升但从 8 加到 16不仅没有继续加速总耗时反而变长了。原因是当 16 个并发 Task 同时在 Oracle 上做范围扫描时数据库的 CPU、IO 都被打满查询产生大量等待单条 SQL 变慢整体吞吐量反而掉下来。4.3 怎么找到“当前环境的最优并发”根据我的经验最优并发不会出现在某个固定数值上而是要综合看三条线。第一条线是源库负载。Oracle 的 CPU 使用率尽量控制在 70% 以下给业务留出余量。这里采集的是同步期间的峰值如果峰值长期超过 80%我建议降一档并发。第二条线是目标库写入能力。MySQL 写入瓶颈往往不在 SQL 本身而在磁盘 IO 和 binlog 落盘。可以用iostat、dstat看目标库的磁盘利用率如果%util长期接近 100%再往上加并发只是把压力转移到了 MySQL。第三条线是网络带宽。千兆内网理论上有 100MB/s 左右但实际能够稳定跑到 50MB/s 就非常不错了。如果你看到总流量已经逼近网卡上限并发加得再多也没有意义。所以实操时我的方法是从 channel2 开始跑往上翻倍试探每档看一次源库 CPU 峰值和总体耗时当耗时不再下降或源库负载超过 75% 时往回退一档再用这一档长期跑。大多数表在 channel4 到 channel8 之间就能获得不错的性价比。5. 实战最容易踩的坑Oracle 源端和 MySQL 目标端的边界条件配置跑通只是第一步。真正让人头疼的是那些“能跑但结果不对”或者“跑到一半报错”的情况。下面这些坑我基本都踩过每一项都值得你在上线前检查一遍。5.1 splitPk 选择不当导致的漏数据和数据倾斜分片字段不是随便挑一个字段就行。最理想的是数值型主键值单调递增、分布均匀、非空。如果选了分布不均匀的字段比如状态字段只有 0 和 1 两个值DataX 切分出来的区间就只有一个能查到大量数据其他区间几乎为空最终还是相当于单线程跑并发完全失效。还有一个很容易踩的坑是分片字段存在 NULL。DataX 在做区间切分时对 NULL 值的处理并不友好可能出现边界条件漏数据甚至直接报错。所以我一般建议如果你的表在分片字段上有 NULL要么在 SQL 里用NVL(ID, 0)处理要么换一个非空字段。有人会说时间字段能不能作为分片字段我的答案是能用但慎用。时间字段如果重复值多或者时区不一致切分后可能产生区间重叠和数据漏读。线上环境我一般只用 ID 或唯一数字序列做分片靠谱得多。5.2 Oracle 和 MySQL 类型映射的坑两边数据库类型体系差异比想象中大通常不是 DataX 本身的问题而是建表结构没对齐。下表是我整理的核心映射参考Oracle 类型MySQL 推荐类型注意事项NUMBER(18)BIGINT超过 2^31 别用 INTNUMBER(18,2)DECIMAL(18,2)映射成 INT 会丢精度VARCHAR2(2000)VARCHAR(2000) 或 TEXTMySQL 的 varchar 有长度上限超了要建 textDATEDATETIMEOracle date 带时分秒MySQL date 不带务必用 datetimeTIMESTAMPDATETIME注意时区传参jdbcUrl 里加 serverTimezoneCLOBLONGTEXT大文本字段要做字符集校验BLOBLONGBLOB二进制字段看业务是否必须同步最容易中招的是 NUMBER 精度过大。Oracle 里NUMBER(38,0)如果直接映射到 MySQLINT数值一大就会报Out of range value for column整条数据写入失败。我在一次渠道订单同步中就因为这个字段导致不少订单缺失最后靠 errorLimit 里的脏数据记录才定位到。建目标表时宁可字段宽一点也不要刚好卡边界。5.3 字符集、时区、批量写入的边界Oracle 字符集是 ZHS16GBKMySQL 可能建表时用的 utf8mb4两边不一致时最容易出现乱码或者是“字符串截断”报错。DataX 的 JDBC URL 上要明确字符集参数Oracle 端一般通过oracle.jdbc.defaultNChar或连接属性做控制MySQL 端在 jdbcUrl 里加上characterEncodingutf8mb4。记住一个原则连接串上的字符集一定要明确写出来别用数据库默认值。时区问题主要在 Oracle 的TIMESTAMP WITH TIME ZONE类型。同步到 MySQL 的 datetime 字段时如果两边时区不同时间数据会被 JVM 默认时区影响多出 8 个小时或者少 8 个小时。我建议在启动 DataX 的 JVM 参数里统一指定时区比如-Duser.timezoneAsia/Shanghai别依赖操作系统默认时区。批量写入也有讲究。MySQL Writer 的batchSize默认值是 1024设大一点能明显提升写入效率但也不是越大越好。我实测 2048 到 4096 是比较合适的区间再大容易撑爆 MySQL 的 max_allowed_packet出现Packet for query is too large报错。批量写入还建议在 jdbcUrl 上加rewriteBatchedStatementstrue这个参数能让 JDBC 把多条 insert 合并提交性能提升非常可观。5.4 常见报错与排查方向汇总遇到报错先不要急按下面的表格对照排查报错特征大概率原因排查动作ORA-12899 value too largeOracle 源字段长于 MySQL 目标字段检查两边字段长度定义加宽目标表Out of range value for columnNUMBER 精度映射错误目标列改成 DECIMAL 或 BIGINTPacket for query is too largebatchSize 太大或字段内容超大调小 batchSize检查是否有超长文本Communications link failure网络抖动或数据库连接被断开检查网络稳定性适当降低并发乱码字符集不一致统一 JDBC 连接串字符集参数同步缺失部分数据splitPk 有 NULL 或分片不均匀检查分片字段空值换主键字段DataX 日志里报错信息一般会带上具体插件和行号比如oraclereader-0表示第 0 个并发分片。如果 errorLimit 配置允许了脏数据比例DataX 会把失败记录写到日志里但业务表数据已经缺失所以生产环境我一般把record设成 0宁可任务失败也不允许静默丢数。6. 从“跑通一次”到“长期稳定同步”的运维习惯最后一个环节是让任务活下来而不是跑一次就完事。这部分更多是运维经验数据同步的稳定性问题几乎都集中在调度和监控上。6.1 DataX-Web 里的调度与执行日志DataX-Web 支持 cron 表达式调度。我的标准配法是全量同步放凌晨业务低峰cron 表达式写清楚分钟和小时比如0 2 * * *表示每天凌晨 2 点。增量同步则根据业务时效要求调整频率有的半小时一次有的一天一次。任务运行后一定要养成看执行日志的习惯。DataX 每次任务结束都会打印“任务启动时刻”、“任务结束时刻”、“任务总计耗时”还会输出平均流量和错误记录数。如果平均流量明显低于历史水平即使任务显示成功我也建议去查一下看是不是源库加了慢查询或者网络出了问题。日志里另外一个有用信息是脏数据统计。DataX 会明确打印脏数据条数和占比如果百分比不断上升说明源端或目标端的数据质量出现了变化早发现早处理。6.2 增量同步怎么设计才稳健全量同步用truncate insert没问题但对大表来说每次全量成本太高。到了后期我更推荐用增量同步来做日常更新。增量同步的核心思路是加一个时间水位线。在 DataX-Web 的任务参数里每次调度时传入一个业务日期或者上次同步时间然后在 reader 的 where 条件里用它过滤数据。where: CREATE_TIME to_date(${lastSyncTime}, yyyy-mm-dd hh24:mi:ss)任务参数通过 DataX-Web 的运行参数传入类似-DlastSyncTime2024-08-01 00:00:00。上次同步时间可以查目标表里的 max(CREATE_TIME)也可以在调度外层用一个状态位维护。增量字段建议选择有索引的时间列否则每次增量查询全表扫一遍代价比全量还大。增量同步最怕的是时间字段回拨或者数据补录。业务人员手工改了一条历史订单的时间会导致这条数据不在增量窗口内。所以很多团队会保留每周一次全量作为兜底配合每日增量这个组合在工程上非常实用。6.3 我在实际维护中总结的几条经验同步任务谁都会配但能稳定跑半年不出问题靠的是细节。第一DataX 所在服务器的磁盘空间要盯紧。DataX 本身不落业务数据但日志文件会一直增长。DataX-Web 的执行器日志如果不定期清理几个月就能吃掉几个 GB。我在日志目录上挂了定时清理任务只保留最近 30 天。第二不要把执行器跟其他重负载应用混布。同步任务吃 IO 和 CPU和数据量大的在线服务放在一起两边都不痛快。独立机器、独立资源是最省心的。第三任何配置改动都要先在测试表上跑一遍。DataX 的 JSON 配置看着简单但一个括号写错就会导致任务解析失败如果直接在线上改又赶上没人在场问题只能等到次日数据对账时才发现。最后一个小技巧在 DataX-Web 的任务名称上把业务含义、同步周期、目标表名写清楚比如“orders全量同步_每日凌晨2点_写入orders报表库”。听起来很简单但当你维护几十个任务时一个清晰的任务命名能节省大量排查时间。数据同步这件事本身不难难的是把每一步都做得规范让系统在没有人工干预的情况下也能按预期运转。