乐于分享
好东西不私藏

DataX 迁库踩坑记:5 个官方文档没写的坑

DataX 迁库踩坑记:5 个官方文档没写的坑
原创 · 踩坑实录
DataX 迁库踩坑记:5 个官方文档没写的坑

从 PostgreSQL 到 MySQL,164 张表的血泪全量迁移。每一个坑,都是我亲手踩出来的。

码上探查数据库迁移2026-08-19

上个月我接了个活儿:把审计库从PostgreSQL(Vastbase G100)全量迁到PolarDB(MySQL 兼容模式),一共164 张表。工具选了阿里的DataX——社区里都说它"开箱即用、配置驱动、稳得一批"。

我信了。然后被现实连续教做人。

这篇文章不是教程,是一份避坑地图。下面 5 个坑,按我踩到的顺序排列,每一个都附带正确做法。如果你也在做 PG → MySQL 的 DataX 迁移,建议先收藏,照着走一遍能省下我那三天的冤枉时间。

1
插件名之坑:Framework-12 是个"指错路的骗子"
报错信息把你引向错误的方向

脚本一跑,第一条日志就炸了。报错长得像这样——

sync_all.sh
ERROR Code:[Framework-12]DataX插件初始化错误,该问题通常是由于DataX安装错误引起。 未完成指定插件加载:[rdbmsreader, jdbcwriter]

这行报错最阴的地方在于:它同时报了rdbmsreaderjdbcwriter两个名字,还补了一句"通常是由于安装错误"。我顺着线索查了三处——

查 rdbmsreader 驱动JAR 注册完全正确清理 ._ 隐藏文件Linux 压根没有这问题差点重装 DataX两插件同时坏?太巧ls plugin/writer/目录里是 mysqlwriter不是 jdbcwriter改 name: "mysqlwriter"2 分钟解决前面 2 小时全是弯路
图 1 · 我花了 2 小时走的冤枉路,其实 1 行 ls 就能绕开

根因简单到离谱:PolarDB 走 MySQL 协议,DataX 的 writer 必须显式写mysqlwriter。我照着直觉写了jdbcwriter——一个根本不存在的插件名。DataX 在plugin/writer/目录里扫不到对应文件夹,直接抛 Framework-12。

为什么这么误导人?Framework-12 的含义是"插件加载失败",它不区分"JAR 坏了"还是"名字不存在"。而报错里同时挂着rdbmsreader,让你本能地去查 reader 的驱动——因为"需要手动注册驱动"这个认知太根深蒂固了。真正的元凶,恰恰是你最不会怀疑的 writer。
坑一教训

再看到 Framework-12,第一反应不是查驱动,而是ls plugin/reader/ls plugin/writer/,把目录名和 JSON 里写的name一字一字对账。这一步不花 1 分钟,能省掉我那 2 小时。

可保存 / 转发给在做 DataX 迁移的同事
下一个坑,藏在表结构里
2
SQL 方言之坑:PG 导出的建表语句,MySQL 跑不动
DataX 只搬数据,不搬表结构

插件名搞定后,我以为稳了。结果建表这一步就卡住——从 PostgreSQLpg_dump出来的 DDL,往 MySQL 上一跑,成片飘红。

最典型的就是IF EXISTS/IF NOT EXISTS。在 PG 里这俩是标配,但MySQL 在ALTER TABLE里根本不支持(MySQL 8.0 的CREATE TABLE IF NOT EXISTS可以,但ALTER TABLE ... ADD COLUMN IF NOT EXISTS至今不支持)。我迁移脚本里为了"幂等"到处写的IF NOT EXISTS,在 MySQL 端全成了语法错误。

PostgreSQL DDL(源)
CREATE TABLE IF NOT EXISTSaudit_log (   idSERIALPRIMARY KEY,   user_idINT,   detailTEXT,   amountNUMERIC(12,2),   created_atTIMESTAMPTZDEFAULT NOW());ALTER TABLEaudit_logADD COLUMN IF NOT EXISTSremarkTEXT;

把这段直接丢给 MySQL,会依次撞上:IF NOT EXISTS不支持、SERIAL不认识、TIMESTAMPTZ不认识。三个错叠在一起,报错信息还互相掩盖,排查起来很乱。

MySQL DDL(目标,改写后)
CREATE TABLEaudit_log (   idINTAUTO_INCREMENTPRIMARY KEY,   user_idINT,   detailLONGTEXT,   amountDECIMAL(12,2),   created_atDATETIMEDEFAULT CURRENT_TIMESTAMP)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- MySQL 不支持 ADD COLUMN IF NOT EXISTS,需先判断ALTER TABLEaudit_logADD COLUMNremarkLONGTEXT;
PostgreSQL 写法SERIAL → INT AUTO_INCREMENTTIMESTAMPTZ → DATETIMETEXT → LONGTEXTIF NOT EXISTS → 需手动判断类型多、方言自由MySQL 写法AUTO_INCREMENT 替代自增DATETIME 不带时区LONGTEXT 存长文本ALTER 无 IF NOT EXISTS需显式 charset/engine
图 2 · 同一张表,两种方言:迁移前必须做一次 DDL 改写
关键认知:DataX 是"数据搬运工",不是" schema 迁移工具"。目标库的表结构得你自己先建好。如果你指望 DataX 自动建表,它会直接用 reader 推断的类型建一张"四不像"的表,后面全是坑。
坑二教训

迁移前写个一键改写脚本:批量剥离IF EXISTS/IF NOT EXISTSSERIALAUTO_INCREMENTTIMESTAMPTZDATETIME,并强制补DEFAULT CHARSET=utf8mb4。把 DDL 改写在跑 DataX 之前,别等数据搬一半才发现表建错了。

可保存 / 转发给在做 DataX 迁移的同事
表建好了,数据却"悄悄"变了样
3
类型映射之坑:DataX 的"自动转换"会静默丢数据
最危险的一类坑,因为不报错

前面两个坑至少会"报错提醒你"。类型映射这个坑最阴——它不报错,数据却悄悄变了。

DataX 在 PG 和 MySQL 之间搬数据时,会按自己的类型映射表做隐式转换。大部分时候没问题,但 PG 有几个"特色类型"到了 MySQL 会出现精度丢失、截断、甚至映射成完全错误的类型:

PostgreSQLMySQL(正确)踩坑点serial / bigserialint / bigint AUTO_INCREMENT漏写自增,主键冲突uuidchar(36)映射成 varchar 截断jsonb / jsonjson (5.7+)低版本无 json 类型numeric(p,s)decimal(p,s)精度不一致丢小数timestamptzdatetime(先转 UTC)时区偏移错乱
图 3 · PG 特色类型 → MySQL 映射表:5 个最容易翻车的地方

我踩得最狠的是两个:

一是numeric精度。源库金额字段是numeric(12,2),我目标库图省事建了decimal(10,2)。数据一搬,超过 10 位整数部分的直接被截断——账面瞬间"少了"几个亿。更可怕的是 DataX 不报任何错,你只在月底对账时才发现。

二是timestamptz时区。PG 的时间带时区,MySQL 的datetime不带。如果源库服务器是 UTC+8、目标库按 UTC 存,直接搬会把时间整体偏移 8 小时。审计日志的时间全错,这种问题平时看不出来,出事就是大事。

为什么它最危险?报错类的坑你当场就能发现;类型映射坑是"搬完了、跑通了、看起来一切正常",但数据已经静默失真。等你发现,原始数据可能都已经清理了。
坑三教训

迁移前做一次列级类型审计:对每一列建立显式映射规则,重点盯numeric精度和timestamptz时区。搬完先用COUNT(*)和抽样CHECKSUM做源端目标端比对,确认行数一致、关键字段值一致,再放行。

可保存 / 转发给在做 DataX 迁移的同事
数据对得上了,速度却让人崩溃
4
批量与性能之坑:164 张表,大表一张跑两小时
默认配置下,DataX 慢得不像话

表建对、数据对,信心又回来了。然后我点了"全量同步"——然后就去吃了顿饭,回来发现才跑了 3 张表。

164 张表里,小的几十行,大的几千万行。问题出在三点:

慢 · 默认单 channel 串行大表无 splitPk提速 · 中speed.channel: 4但无 splitPk → 不生效快 · 正确channel + splitPk大表自动分片关键认知:只配 channel 不加 splitPk,DataX 无法对单表分片,多通道形同虚设;splitPk 必须是整型主键(如 id),DataX 才能按区间并发读取。
图 4 · DataX 提速三步走:channel 和 splitPk 要配套

我一度以为"多配几个speed.channel就能快",结果没配splitPk,DataX 根本没法把单张大表切成多段并发读,多通道白配。正确姿势是:大表设splitPk(一般选整型主键id),DataX 按主键区间把表切成 N 段并行搬运;小表直接并发多 job 跑。

还有两个隐藏坑:① 一次性全量跑,中间网络抖一下断了,得从头再来——没有断点续传,所以要用 shell 循环逐表跑、成功一张记一张;② 并发开太大,源库和目标库的连接数、CPU 会被打满,反而更慢甚至被限流。提速是门平衡术。
坑四教训

把 164 张表按数据量分级:大表配splitPk + channel切片并发;小表用脚本并发多 job。每跑完一张写进度文件,断了从断点续跑。别迷信"一键全量",可控的分批才是生产级做法。

终于跑完了,但还没完
5
收尾之坑:字符集、时区、自增,全在最后一公里埋雷
数据搬完 ≠ 迁移完成

所有表都跑通了,我长舒一口气。结果验收时又发现三件事:

① 中文变问号。目标库有几张表建表时没显式指定字符集,继承了实例默认的latin1。PG 里好好的中文,搬过来全成了???。后来统一改成utf8mb4重建才救回来。

② 自增断层。全量插入后,AUTO_INCREMENT的当前值没跟上最大 id。应用一插入新数据,主键直接撞上已有值报重复——或者产生巨大断层。必须手动ALTER TABLE ... AUTO_INCREMENT = max_id + 1

③ 外键顺序。带外键的表如果按字母顺序导入,子表先于父表到达,约束直接报错。得先按依赖关系拓扑排序,父表先导入。

迁移后必做校验✓ 行数:COUNT 源端 = 目标端✓ 字符集:全部 utf8mb4✓ 抽样:关键字段值一致容易漏的补丁⚠ AUTO_INCREMENT 重置⚠ 外键按依赖顺序导入⚠ 时区抽样比对
图 5 · 迁移完成 ≠ 验收通过:两张清单对照着勾
坑五教训

把"迁完"和"验收通过"分开。数据落库后跑一组验收 SQL:行数比对、字符集确认、自增重置、外键顺序、时区抽样。这五步不勾完,别在周报里写"迁移完成"。

写在最后
如果明天你也要做 PG → MySQL 迁移,照这个顺序走
  1. 先对账插件名
    ls plugin/writer/,确认mysqlwriter不是jdbcwriter
  2. 先改写 DDL
    :剥离IF NOT EXISTSSERIAL→AUTO_INCREMENT,补utf8mb4,建表再搬数。
  3. 先做类型审计
    :列级映射,盯死numeric精度和timestamptz时区。
  4. 再配并发
    :大表splitPk + channel,分批跑、记进度、可续传。
  5. 最后验收
    :行数、字符集、自增、外键、时区,五步走完再收工。

DataX 本身不难,难的是它"假装"帮你做了很多事——类型转换、建表、分片,看起来全自动,背后全是默认行为在替你做主。踩完这 5 个坑我最大的体会是:迁移工具越"自动",越要在跑之前把每一步的"自动"想清楚。

你迁库时踩过最阴的坑是什么?
评论区聊聊,我挨个回。下篇打算写「DataX 增量同步怎么做到准实时」,想看的扣 1。
回复 datax 领迁移 DDL 改写脚本
码上探查
每次踩坑,都替你记一笔原创内容 · 转载请注明出处