1. MySQL2PG 异构迁移到底难在哪MySQL 到 PostgreSQL 的异构迁移听起来只是换个数据库实际动手你会发现坑比想象中多。MySQL2PG 是一个用 Golang 写的开源转换工具专门解决 MySQL 到 PostgreSQL 的表结构、数据、索引、用户、权限的自动化迁移问题。它适合谁适合手头有一批 MySQL 库表要迁到 PG、又不想一张张手动改 DDL 的后端开发和 DBA。我自己经历过一次 1300 张表的迁移最头疼的不是数据量而是类型映射。MySQL 的tinyint(1)到 PG 要变成BOOLEANdatetime要处理时区自增列AUTO_INCREMENT在 PG 里得换成SERIAL或GENERATED。还有索引MySQL 的复合索引顺序和 PG 不完全一样主键、唯一索引、普通索引加起来上千个手动重建基本不现实。更麻烦的是用户和权限。MySQL 的用户体系是userhost的形式PG 里对应的是 role密码加密方式也不同。如果库里有几十个用户、每个用户权限还不一样手动 GRANT 一遍能让人崩溃。MySQL2PG 把这些步骤串成了一条流水线先转表结构再同步数据然后重建索引接着转用户和权限最后做数据校验。整个过程有进度百分比跑完还有各阶段耗时汇总。这篇文章我会带你从零跑通一次可复现的迁移先配好config.toml的连接和映射骨架再通过 TaoToken 的统一 Key 通道接入模型能力辅助排查迁移中的报错最后用行数对比和抽样数据验证迁移结果。全程命令和配置都能直接复制。2. TaoToken 前置统一 Key 与 API 通道准备在迁移过程中你可能会遇到各种报错比如类型不兼容、权限授予失败、数据校验不一致。这时候如果有一个能快速解释报错、生成修复 SQL 的模型通道效率会高很多。TaoToken 提供的就是这样一个统一入口一个 Key 走通模型对话、编码辅助和 API 调用不用在多个平台之间来回切换。你需要先拿到 API Key。访问控制台创建# 控制台地址创建和管理 Key https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite创建完 Key 后API 的基础地址是https://taotoken.net/api这个地址不加 UTM 参数直接用于代码里的base_url配置。如果你用的是 OpenAI 兼容的 SDK把base_url指向它就行。模型对话入口在这里https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite对于长期做编码和 Agent 任务的场景Coding Plan 会更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteAPI Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite拿到 Key 之后你可以用 curl 先验证通道是否通curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: MySQL tinyint(1) 迁移到 PostgreSQL 应该用什么类型}] }如果返回正常的 JSON 响应说明 Key 和通道都没问题。这一步很重要因为后面迁移报错时你要靠这个通道快速定位问题。3. config.toml 连接与映射骨架MySQL2PG 的配置文件是config.toml有些版本用config.yml结构类似。下面是一个可以直接用的骨架我按连接、转换开关、映射规则三块拆开讲。3.1 数据库连接配置[mysql] host 127.0.0.1 port 3306 user root password your_mysql_password database source_db charset utf8mb4 test_only false [postgresql] host 127.0.0.1 port 5432 user postgres password your_pg_password database target_db sslmode disable test_only falsetest_only这个参数很实用。把它设成true工具只测试连接并打印版本信息不执行任何转换。迁移前先跑一次确认两边都能连上./mysql2pg --config config.toml输出会显示 MySQL 和 PostgreSQL 的版本比如 MySQL 5.7.44 和 PostgreSQL 16.1。版本信息对后续排查类型兼容问题很关键。3.2 转换开关与映射规则[conversion] tableddl true data true indexes true users true table_privileges true validate_data true truncate_before_sync true use_table_list false table_list [user, orders, products] skip_existing_tables true lowercase_columns false max_rows_per_batch 5000 batch_insert_size 1000 concurrency 4 max_indexes_per_batch 20几个关键参数说明truncate_before_sync设成true时同步前会清空目标表避免数据重复。设成false则保留已有数据可能出现主键冲突这时候校验会提示不一致。use_table_list配合table_list使用只同步你关心的表。比如你只想迁user和orders就把它设成true列表里写表名。lowercase_columns控制字段名是否转小写。PG 默认把未加引号的标识符转小写如果你的 MySQL 表有大小写混合的字段名这里要注意。concurrency是并发线程数数据量大时可以调高但别超过数据库连接池上限。3.3 类型映射对照MySQL2PG 内置了类型映射但有些边界情况需要你手动确认。下面是我实测下来常见的映射关系MySQL 类型PostgreSQL 类型注意事项tinyint(1)BOOLEAN自动识别tinyintSMALLINT无符号时注意范围int / integerINTEGER自增转 SERIALbigintBIGINT自增转 BIGSERIALdatetimeTIMESTAMP时区需确认varchar(n)VARCHAR(n)长度一致textTEXT直接映射enumVARCHAR CHECK需手动确认约束jsonJSONB建议用 JSONB如果遇到工具没覆盖的类型你可以在转换后用 TaoToken 的模型对话快速生成 ALTER 语句https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite把报错信息和表结构贴进去让它给出 PG 兼容的 DDL。4. 迁移执行与验证请求配置写好后直接运行./mysql2pg --config config.toml工具会按顺序执行表结构转换、数据同步、索引重建、用户转换、权限授予、数据校验。控制台会打印进度百分比比如2. 同步表数据... 进度: 17.24% (5/29) : 同步表 acl_space 数据成功共有 0 行数据数据校验一致 进度: 20.69% (6/29) : 同步表 users_20251201 数据成功共有 1 行数据数据校验一致跑完后会输出各阶段耗时汇总----------------------------------------------------------------- | 阶段 | 对象数量 | 耗时(秒) | ----------------------------------------------------------------- | 转换表结构 | 4 | 0.84 | | 同步表数据 | 4 | 1.69 | | 转换表索引 | 1 | 0.05 | | 转换库用户 | 5 | 0.24 | | 转换表权限 | 15 | 1.41 | ----------------------------------------------------------------- | 总耗时 | | 4.23 | -----------------------------------------------------------------4.1 行数校验迁移完成后第一件事是对行数。在 MySQL 和 PG 分别执行-- MySQL 侧 SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema source_db ORDER BY table_name; -- PostgreSQL 侧 SELECT relname AS table_name, n_live_tup AS row_count FROM pg_stat_user_tables ORDER BY relname;注意 MySQL 的table_rows是估算值InnoDB 下不精确。要精确对比用COUNT(*)SELECT COUNT(*) FROM source_db.users_20251201;PG 侧SELECT COUNT(*) FROM target_db.users_20251201;如果两边数字不一致工具会在日志里输出「数据量校验不一致的表统计」表格直接告诉你哪张表差了多少行。4.2 抽样数据校验行数对了不代表数据对。抽几条记录对比字段值-- MySQL SELECT id, username, created_at FROM source_db.users_20251201 LIMIT 5; -- PostgreSQL SELECT id, username, created_at FROM target_db.users_20251201 LIMIT 5;重点看几个地方自增 ID 是否连续、时间字段是否有时区偏移、布尔字段是否正确转换、中文字符是否乱码。如果发现created_at差了 8 小时说明时区没对齐需要在 PG 连接里设置timezone。4.3 用 TaoToken 辅助校验如果抽样发现异常可以把两边的查询结果贴到模型对话里让它帮你分析差异原因https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite比如你发现某个enum字段在 PG 里变成了空值模型会提示你检查 CHECK 约束是否覆盖了所有枚举值。5. 本篇常见错排查5.1 主键冲突duplicate key value violates unique constraint报错长这样ERROR: duplicate key value violates unique constraint users_20251201_pkey (SQLSTATE 23505)原因通常是truncate_before_sync false目标表里已有数据再次插入就撞了主键。解决办法有两个把truncate_before_sync设成true重新跑或者手动清空目标表TRUNCATE TABLE target_db.users_20251201 RESTART IDENTITY CASCADE;RESTART IDENTITY会把自增序列重置避免后续插入 ID 不连续。5.2 表在 PostgreSQL 中不存在跳过权限授予日志里出现表 users 在PostgreSQL中不存在跳过权限授予这说明表结构转换阶段没成功创建这张表。回去看tableddl阶段的日志可能是 DDL 里有 PG 不支持的语法比如ENGINEInnoDB或CHARSETutf8mb4。MySQL2PG 会尝试剥离这些但复杂 DDL 可能漏掉。手动改一下-- 把 MySQL 的建表语句里的 ENGINE 和 CHARSET 去掉 CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(64) NOT NULL );5.3 类型不兼容column xxx cannot be cast automatically这种报错一般出现在数据同步阶段MySQL 的某个字段类型在 PG 里没有直接对应。比如 MySQL 的unsigned int超出 PGINTEGER范围。解决办法是先在 PG 里把目标列改成BIGINT再重新同步ALTER TABLE target_db.orders ALTER COLUMN amount TYPE BIGINT;5.4 连接超时或认证失败如果test_only阶段就报连接错误先检查pg_hba.conf是否允许你的 IP 连接以及密码是否正确。PG 默认的scram-sha-256认证和 MySQL 的mysql_native_password不同确认用户密码加密方式匹配。5.5 日志文件定位工具会在当前目录生成两个日志errors.log记录错误conversion.log记录完整过程。排查时先看errors.log的最后几行再到conversion.log里搜对应的表名能看到上下文。6. 接入方式与后续动作迁移跑通之后如果你要长期做这类异构转换建议把 TaoToken 的 API 通道集成到你的运维脚本里。比如写一个 Python 脚本自动读取errors.log把报错发给模型拿到修复建议后输出到控制台import os import requests TAOTOKEN_KEY os.environ[TAOTOKEN_API_KEY] API_URL https://taotoken.net/api/v1/chat/completions def analyze_error(error_text): resp requests.post( API_URL, headers{Authorization: fBearer {TAOTOKEN_KEY}}, json{ model: gpt-4o-mini, messages: [ {role: system, content: 你是数据库迁移专家用简洁的中文给出修复步骤。}, {role: user, content: error_text} ] } ) return resp.json()[choices][0][message][content] with open(errors.log, r) as f: errors f.read() if errors.strip(): print(analyze_error(errors[-2000:]))API Key 在控制台管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite接入文档里有更多语言示例https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你主要用 Claude Code 做编码辅助Anthropic 兼容通道的配置在这里https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentClaudeCodeAnthropicutm_campaignrewrite最后提醒一句迁移前一定要在测试库跑一遍全流程确认行数和抽样数据都一致再上生产。truncate_before_sync在生产环境慎用最好先备份目标库。