1. MySQL2PG 异构数据库转换为什么手动改 DDL 总会踩坑MySQL 到 PostgreSQL 的迁移很多人第一反应是「用 pgloader 或者 Navicat 导一下不就行了」。真到业务库上你会发现事情没那么简单tinyint(1)到了 PG 变成 smallint 而不是 booleanAUTO_INCREMENT要换成GENERATED BY DEFAULT AS IDENTITY反引号要全删ENGINEInnoDB和CHARSETutf8mb4这些子句 PG 根本不认。表一多几百上千张表靠手工改 DDL 基本是体力活而且改错一个字段类型后面数据同步就会报类型不匹配。MySQL2PG 就是冲着这个痛点来的它是一个用 Go 写的命令行工具读取 MySQL 的表定义自动做字段类型映射在 PostgreSQL 里建表然后分批把数据搬过去再重建索引、同步用户和表权限最后做行数校验。适合谁适合手上有几十到上千张表的 MySQL 业务库、想整体迁到 PostgreSQL、又不想逐表写转换脚本的开发者。它不替代你的编辑器也不做在线双写定位是一次可复现的库级迁移演练。我试过拿一个 1300 多张表的库跑全流程最直观的感受是「进度可视化」这件事很关键——你能看到第几张表、同步了多少行、哪张表校验不一致而不是干等一个黑盒。下面按「准备配置 → 跑起来 → 验证 → 排错」的顺序把可复制的步骤拆开讲。先明确几个概念避免后面看配置发懵。表结构转换指把 MySQL 的CREATE TABLE翻译成 PG 能执行的 DDL数据同步指按批把行读出来插到 PG索引转换指主键、唯一索引、普通索引在 PG 侧重建用户与权限转换指把 MySQL 的账号和 GRANT 映射成 PG 的 role 和 GRANT。这四件事在配置文件里是独立开关你可以只开表结构、只开数据也可以全开跑一遍完整迁移。还有一个容易忽略的点MySQL 和 PostgreSQL 的标识符大小写规则不同。MySQL 在 Linux 上表名默认区分大小写PG 则会把未加引号的标识符全部折叠成小写。所以工具里有个lowercase_columns开关控制字段名是否统一转小写。如果你的业务代码里有驼峰字段名迁移前一定要想清楚这一层否则应用连上来会找不到列。2. 前置准备TaoToken 配置与 MySQL2PG 运行环境这一节分两块一块是 MySQL2PG 本身的运行环境一块是如果你打算用 AI 辅助写迁移脚本、生成校验 SQL 时怎么把模型调用配好。两块互不依赖按需取用。2.1 MySQL2PG 的环境与获取工具是 Go 写的最省事的方式是直接拿编译好的二进制跨平台、启动快、内存占用低这也是原作者选 Go 的原因。你需要准备一个能连通的 MySQL 实例测试用 5.7.x 即可生产建议先确认版本一个 PostgreSQL 实例16.x 实测可用目标 PG 库里提前建好 database工具只建表不建库MySQL 账号需要有读取表结构、读取数据、读取mysql.user和权限表的权限源码仓库在 GitHub 上xfg0218/MySQL2PG你可以git clone后自己go build也可以直接用 release 里的二进制。编译命令很直接git clone https://github.com/xfg0218/MySQL2PG.git cd MySQL2PG go mod tidy go build -o mysql2pg .编译完当前目录会多一个mysql2pg可执行文件。运行前确认config.yml和它在同一目录工具默认读当前路径的配置日志也写在当前路径。2.2 用 TaoToken 辅助生成迁移与校验脚本迁移过程中经常要临时写一些校验 SQL比如「对比两张表在 MySQL 和 PG 里的行数」「找出 PG 里缺失的索引」。这类一次性脚本用模型生成很合适。TaoToken 提供统一的模型调用入口把 Base URL 指向它、填上 Key、选好 Model ID 就能用不用分别去各家平台开账号。三件套这样配Base URL 填https://taotoken.net/apiAPI Key 在控制台的 API Keys 页面创建Model ID 按你选的模型填比如对话类或代码类模型的具体名称。如果你用的是 Claude Code 这类编码工具走的是 Anthropic 兼容入口配置方式略有不同参考文档里的 ClaudeCodeAnthropic 说明即可。需要提醒的是模型调用只用来生成辅助脚本和排查思路真正的迁移执行还是靠 MySQL2PG 本地跑别把生产库连接信息贴进任何对话里。生成完脚本自己 review 一遍再执行这是基本习惯。3. 可复制配置config.yml 逐项拆解MySQL2PG 的所有行为都由config.yml驱动。下面这份配置是按「全流程跑一遍」写的你可以按需关掉某些阶段。注意 YAML 的缩进层级错了工具会读不到。mysql: host: 127.0.0.1 port: 3306 user: migrate_user password: your_mysql_password database: biz_db charset: utf8mb4 test_only: false postgresql: host: 127.0.0.1 port: 5432 user: postgres password: your_pg_password database: biz_db_pg sslmode: disable test_only: false conversion: tableddl: true data: true indexes: true functions: false users: true table_privileges: true validate_data: true lowercase_columns: true skip_existing_tables: true truncate_before_sync: true use_table_list: false table_list: - user - orders max_rows_per_batch: 1000 batch_insert_size: 500 concurrency: 4 max_indexes_per_batch: 20逐项说关键参数。test_only放在 mysql 和 postgresql 两个块里任意一个设为 true工具就只测对应连接、打印版本信息然后退出适合迁移前先确认网络和账号没问题。tableddl控制建表skip_existing_tables为 true 时目标已存在的表会跳过而不是报错重复跑很友好。truncate_before_sync建议第一次迁移设为 true先清空目标表再灌数据避免主键冲突如果你做的是增量补数设 false 但要接受可能出现重复行。use_table_list配合table_list用只同步你关心的表适合先拿几张核心表试水。max_rows_per_batch是每次从 MySQL 读多少行batch_insert_size是每批往 PG 插多少行concurrency是并发线程数。这三个值要根据你的网络延迟和库负载调默认值偏保守表特别大时可以适当加大但别一上来就拉满。lowercase_columns前面提过统一转小写能规避大小写折叠问题但如果你的应用层 SQL 里写死了带引号的驼峰列名就要设 false 并自己保证一致性。functions目前版本对 MySQL FUNCTION 的自动转换还不完整建议先关掉函数单独处理。配置写完后先跑一次只测连接的./mysql2pg如果两个test_only都是 false它会直接进入转换流程。想单独验证连接把其中一个设 true 再跑控制台会打印类似这样的版本对照表------------------------------------------------------------ | 数据库类型 | 版本信息 | ------------------------------------------------------------ | MySQL | 5.7.44 | | PostgreSQL | PostgreSQL 16.1 on x86_64-pc-linux-gnu... | ------------------------------------------------------------看到这张表说明账号、网络、库名都对上了可以往下走。4. 执行迁移与验证从建表到行数一致性校验配置确认无误后直接运行./mysql2pg工具会按 tableddl → data → indexes → users → table_privileges → validate_data 的顺序执行。控制台会输出带百分比的进度比如「进度: 17.24% (5/29) : 同步表 acl_space 数据成功共有 0 行数据数据校验一致」。这个百分比是按对象总数算的表多的时候能直观看到还剩多少。4.1 分阶段观察输出表结构阶段已存在的表会提示「表 xxx 已存在跳过创建」这是skip_existing_tables生效。数据同步阶段每张表同步完会立刻做一次行数比对一致就标「数据校验一致」。索引阶段会逐条打印「转换索引 idx_user_id 成功」。用户和权限阶段会打印「转换用户 user1% 的权限成功」这类信息。全部跑完后控制台会给一张各阶段耗时汇总表----------------------------------------------------------------- | 阶段 | 对象数量 | 耗时(秒) | ----------------------------------------------------------------- | 转换表结构 | 1339 | ... | | 同步表数据 | 1339 | ... | | 转换表索引 | 1768 | ... | | 转换库用户 | 5 | ... | | 转换表权限 | 15 | ... | -----------------------------------------------------------------这张表是你评估迁移窗口的依据。表越多、数据量越大data 阶段占比越高可以针对性调concurrency和批大小。4.2 迁移后的一致性验证动作工具自带的validate_data只比对行数这是第一道关但不够。行数一致不代表内容一致建议再补两步手工校验。第一步抽样比对关键表的字段值。在 MySQL 和 PG 各跑一条聚合 SQL比如对订单表按状态分组计数-- MySQL 侧 SELECT status, COUNT(*) AS cnt FROM orders GROUP BY status ORDER BY status; -- PostgreSQL 侧 SELECT status, COUNT(*) AS cnt FROM orders GROUP BY status ORDER BY status;两边结果贴出来对一遍数量级和分组分布一致基本能排除大批量错位。第二步检查索引是否齐全。在 PG 里查目标表的索引清单SELECT indexname, indexdef FROM pg_indexes WHERE schemaname public AND tablename orders;对照 MySQL 的SHOW INDEX FROM orders;确认主键、唯一索引、复合索引都在。复合索引的列顺序尤其要看顺序错了查询计划会用不上。第三步验证自增列。MySQL 的AUTO_INCREMENT迁到 PG 后是 identity 列插入一条测试数据看主键是否自动递增INSERT INTO orders (user_id, amount) VALUES (1, 99.00) RETURNING id;能返回一个新 id 就说明序列接上了。如果报「null value in column id violates not-null」说明 identity 没建对需要回表结构阶段排查。4.3 指定表同步的用法不想全量跑就把use_table_list设 truetable_list里列上表名。工具只处理这些表进度分母也会变成列表长度。实测两张表的数据同步不到一秒适合反复调试某几张核心表的转换规则。5. 常见报错排查401、duplicate key 与表不存在迁移过程里的报错大致分三类连接认证类、数据冲突类、对象缺失类。下面按真实日志对照说。认证失败401 类。如果你在辅助脚本里调模型接口报 401先查 API Key 是否填对、有没有多余空格、Base URL 是不是https://taotoken.net/api而不是带路径的完整端点。MySQL2PG 本身的连接失败则表现为「Access denied for user」检查config.yml里的账号密码和 MySQL 的 host 授权user%和userlocalhost是两条不同的授权记录。duplicate key value violates unique constraint。这是数据同步阶段最常见的错日志长这样ERROR: 插入表 users_20251201 数据失败: 批量插入失败: ERROR: duplicate key value violates unique constraint users_20251201_pkey (SQLSTATE 23505)原因通常是truncate_before_sync设成了 false目标表里已有旧数据新数据主键撞上了。解决办法二选一把truncate_before_sync改 true 重跑或者手工清空目标表再同步。如果你确实要做增量得自己保证源端只取新增行工具本身不做去重。表在 PostgreSQL 中不存在跳过权限授予。日志里会出现「表 users 在PostgreSQL中不存在跳过权限授予」。这说明权限转换阶段找不到对应表多半是表结构阶段跳过了这张表skip_existing_tables或建表失败或者table_list里没包含它。先确认 PG 里\dt能看到这张表再看表结构阶段的日志有没有报错。数据量校验不一致。当truncate_before_sync为 false 时工具不会因行数不一致中断而是在结尾输出一张统计表---------------------------------------------------- | 表名 | MySQL数据量 | PostgreSQL数据量 | ---------------------------------------------------- | users_20251201 | 1 | 2 | ----------------------------------------------------PG 侧多出来的行就是重复插入的。按表名去 PG 里查主键分布删掉重复行或者干脆清表重跑。日志去哪看。工具默认在当前目录生成errors.log和conversion.log。errors.log只记错误适合快速定位失败点conversion.log记全过程包括「表 xxx 已存在跳过创建」这种信息级日志。排查时先看 errors.log 的最后一条再回 conversion.log 找上下文。字段类型映射不符预期。比如 MySQL 的tinyint(1)你期望变 boolean结果成了 smallint。这跟工具版本和映射规则有关建表后手工ALTER TABLE ... ALTER COLUMN ... TYPE boolean USING ...修正或者迁移前在 MySQL 侧把字段定义调整好。批量修正可以写个脚本遍历information_schema.columns生成 ALTER 语句。6. 把迁移流程固化成可复现的演练一次成功的迁移不算完能重复跑才是关键。建议把整个流程固化成三步先在测试环境用test_only验证连通性再用use_table_list挑三五张核心表跑通全流程最后全量跑并留存conversion.log作为迁移记录。每次改配置后重跑skip_existing_tables和truncate_before_sync的组合决定了是「增量补」还是「清空重灌」这个要想清楚再执行。如果你在迁移过程中需要批量生成校验 SQL、或者让模型帮你解读某段报错日志可以把模型调用配到 TaoToken 的 API 入口Base URL 用https://taotoken.net/apiKey 在控制台创建Model ID 按需选。长期做编码和 Agent 类任务的可以看看 Coding Plan 的额度方案只是偶尔验证模型效果的直接用模型对话页面试就行。接入细节和参数说明在接入文档里有完整示例照着填三件套即可。最后留一个实操建议迁移前在 MySQL 侧执行SHOW CREATE TABLE把关键表的原始 DDL 存一份迁移后在 PG 侧用pg_dump --schema-only导一份对比。两份 DDL 摆在一起看字段类型、默认值、索引的差异一目了然比事后靠应用报错反推高效得多。