从 PostgreSQL 到 MySQL,164 张表的血泪全量迁移。每一个坑,都是我亲手踩出来的。
上个月我接了个活儿:把审计库从PostgreSQL(Vastbase G100)全量迁到PolarDB(MySQL 兼容模式),一共164 张表。工具选了阿里的DataX——社区里都说它"开箱即用、配置驱动、稳得一批"。
我信了。然后被现实连续教做人。
这篇文章不是教程,是一份避坑地图。下面 5 个坑,按我踩到的顺序排列,每一个都附带正确做法。如果你也在做 PG → MySQL 的 DataX 迁移,建议先收藏,照着走一遍能省下我那三天的冤枉时间。
脚本一跑,第一条日志就炸了。报错长得像这样——
这行报错最阴的地方在于:它同时报了rdbmsreader和jdbcwriter两个名字,还补了一句"通常是由于安装错误"。我顺着线索查了三处——
根因简单到离谱:PolarDB 走 MySQL 协议,DataX 的 writer 必须显式写mysqlwriter。我照着直觉写了jdbcwriter——一个根本不存在的插件名。DataX 在plugin/writer/目录里扫不到对应文件夹,直接抛 Framework-12。
rdbmsreader,让你本能地去查 reader 的驱动——因为"需要手动注册驱动"这个认知太根深蒂固了。真正的元凶,恰恰是你最不会怀疑的 writer。再看到 Framework-12,第一反应不是查驱动,而是ls plugin/reader/和ls plugin/writer/,把目录名和 JSON 里写的name一字一字对账。这一步不花 1 分钟,能省掉我那 2 小时。

插件名搞定后,我以为稳了。结果建表这一步就卡住——从 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 端全成了语法错误。
把这段直接丢给 MySQL,会依次撞上:IF NOT EXISTS不支持、SERIAL不认识、TIMESTAMPTZ不认识。三个错叠在一起,报错信息还互相掩盖,排查起来很乱。
迁移前写个一键改写脚本:批量剥离IF EXISTS/IF NOT EXISTS、SERIAL换AUTO_INCREMENT、TIMESTAMPTZ换DATETIME,并强制补DEFAULT CHARSET=utf8mb4。把 DDL 改写在跑 DataX 之前,别等数据搬一半才发现表建错了。

前面两个坑至少会"报错提醒你"。类型映射这个坑最阴——它不报错,数据却悄悄变了。
DataX 在 PG 和 MySQL 之间搬数据时,会按自己的类型映射表做隐式转换。大部分时候没问题,但 PG 有几个"特色类型"到了 MySQL 会出现精度丢失、截断、甚至映射成完全错误的类型:
我踩得最狠的是两个:
一是numeric精度。源库金额字段是numeric(12,2),我目标库图省事建了decimal(10,2)。数据一搬,超过 10 位整数部分的直接被截断——账面瞬间"少了"几个亿。更可怕的是 DataX 不报任何错,你只在月底对账时才发现。
二是timestamptz时区。PG 的时间带时区,MySQL 的datetime不带。如果源库服务器是 UTC+8、目标库按 UTC 存,直接搬会把时间整体偏移 8 小时。审计日志的时间全错,这种问题平时看不出来,出事就是大事。
迁移前做一次列级类型审计:对每一列建立显式映射规则,重点盯numeric精度和timestamptz时区。搬完先用COUNT(*)和抽样CHECKSUM做源端目标端比对,确认行数一致、关键字段值一致,再放行。

表建对、数据对,信心又回来了。然后我点了"全量同步"——然后就去吃了顿饭,回来发现才跑了 3 张表。
164 张表里,小的几十行,大的几千万行。问题出在三点:
我一度以为"多配几个speed.channel就能快",结果没配splitPk,DataX 根本没法把单张大表切成多段并发读,多通道白配。正确姿势是:大表设splitPk(一般选整型主键id),DataX 按主键区间把表切成 N 段并行搬运;小表直接并发多 job 跑。
把 164 张表按数据量分级:大表配splitPk + channel切片并发;小表用脚本并发多 job。每跑完一张写进度文件,断了从断点续跑。别迷信"一键全量",可控的分批才是生产级做法。
所有表都跑通了,我长舒一口气。结果验收时又发现三件事:
① 中文变问号。目标库有几张表建表时没显式指定字符集,继承了实例默认的latin1。PG 里好好的中文,搬过来全成了???。后来统一改成utf8mb4重建才救回来。
② 自增断层。全量插入后,AUTO_INCREMENT的当前值没跟上最大 id。应用一插入新数据,主键直接撞上已有值报重复——或者产生巨大断层。必须手动ALTER TABLE ... AUTO_INCREMENT = max_id + 1。
③ 外键顺序。带外键的表如果按字母顺序导入,子表先于父表到达,约束直接报错。得先按依赖关系拓扑排序,父表先导入。
把"迁完"和"验收通过"分开。数据落库后跑一组验收 SQL:行数比对、字符集确认、自增重置、外键顺序、时区抽样。这五步不勾完,别在周报里写"迁移完成"。
- 先对账插件名
: ls plugin/writer/,确认mysqlwriter不是jdbcwriter。 - 先改写 DDL
:剥离 IF NOT EXISTS,SERIAL→AUTO_INCREMENT,补utf8mb4,建表再搬数。 - 先做类型审计
:列级映射,盯死 numeric精度和timestamptz时区。 - 再配并发
:大表 splitPk + channel,分批跑、记进度、可续传。 - 最后验收
:行数、字符集、自增、外键、时区,五步走完再收工。
DataX 本身不难,难的是它"假装"帮你做了很多事——类型转换、建表、分片,看起来全自动,背后全是默认行为在替你做主。踩完这 5 个坑我最大的体会是:迁移工具越"自动",越要在跑之前把每一步的"自动"想清楚。
夜雨聆风