最近帮一个朋友折腾数据库迁移,他之前用 MySQL,现在想换到 PostgreSQL。说实话,这事儿在圈子里越来越常见了。PostgreSQL 这几年火得不行,尤其是那些搞数据分析的团队,动不动就抱怨 MySQL 在处理复杂查询时卡得要命,而 PG 用一个递归 CTE 就搞定了。但迁移可不是拍脑袋就能干的,我见过太多把数据拷过去就以为完事儿的,结果上线两天就崩了。

先说说最头疼的兼容性问题。MySQL 和 PostgreSQL 虽然都是关系型数据库,但语法差异大到让人抓狂。比如 MySQL 用反引号包字段名,PG 只能用双引号。还有自动类型转换,MySQL 里把字符串和数字比较会自动转型,PG 则直接报错。我的朋友就被这个坑了,他有个表存电话号码,MySQL 里是整数,迁到 PG 后跑出科学计数法,客户电话全变成了 1.23e+10。更别说存储过程、触发器的写法,基本都得重写。所以别指望有工具能一键搞定,老老实实把 SQL 脚本翻一遍,该改的改,该测的测。
数据类型转换更是暗坑无数。MySQL 的 TINYINT 在 PG 里对应 BOOLEAN?别信文档上的对应表,实际跑起来完全两码事。我上次迁移一个电商订单表,里面有个 status 字段,值只有 0、1、2,MySQL 用 TINYINT 存得很好。结果用 pgloader 一导,PG 自动转成 boolean,0 变成 false,1 和 2 都变成 true,订单状态直接乱套,线上查了三天才发现。正确的做法是手动映射:TINYINT → SMALLINT,MEDIUMINT → INTEGER,BIGINT 保持原样。日期类型也别大意,MySQL 的 DATETIME 和 PG 的 TIMESTAMP 看似相同,但时区处理逻辑完全不同,迁移前一定要统一时区设置。
索引迁移这块,很多人直接忽略了。MySQL 的索引类型和 PG 不一样,尤其是全文索引:MySQL 用 FULLTEXT,PG 用 GIN 或 GiST。如果原库里大量使用全文搜索,迁移后索引重建就是个大坑。曾有一个博客平台的同事,迁移后搜索功能直接失效——MySQL 的全文索引默认不分词中文,PG 的 tsvector 又不认他的分词规则,只好硬装 jieba 分词插件,性能下降不少。还有复合索引的列顺序,MySQL 和 PG 的 B‑tree 实现细节不同,同样的定义在 PG 上可能更慢。所以别偷懒,迁移完一定要用 EXPLAIN ANALYZE 重新检查查询计划。
数据量大的时候,迁移速度也是大问题。我见过有人用 pgloader 导 400 GB 的数据,跑了三天三夜才完成,原因是网络带宽被占满。其实有几个技巧可以加速:先建好目标表结构,关闭所有约束和索引,等数据导完再重建。PG 的 COPY 命令比 INSERT 快十倍以上,能不用工具就别用工具。如果数据有几十亿条,考虑分批导入,每批 100 万条,中间加个 sleep 让磁盘喘口气。还有个骚操作:用管道把 MySQL 的 mysqldump 和 PG 的 psql 串起来,跳过中间文件,能省不少 I/O。但要注意字符集,否则导进去全是乱码。
迁移后的性能调优才是重头戏。很多人以为数据导过去就万事大吉,结果发现同样的 SQL 在 PG 上跑得比 MySQL 还慢。这很正常,因为 PG 的优化器逻辑完全不同。比如 MySQL 里用 IN 子查询效果不错,PG 里可能得改成 JOIN 才能得到好的执行计划。我帮一个广告平台调优时,发现他们的统计查询 MySQL 只要 0.5 秒,PG 却要 8 秒。原因是 PG 对 JSON 字段的索引支持不够好,改成 GIN 索引后,时间降到 0.3 秒。还有 autovacuum 参数,默认配置在写密集场景下根本扛不住,需要根据业务调整阈值和频率。别指望 PG 开箱即用,它是个需要调教的数据库。
说说业务中断的问题。线上迁移最怕停服太久,用户会抱怨。我一般推荐双写方案:先搭建一个 PG 从库,把 MySQL 的 binlog 实时同步过去,等数据一致后再切流量。但 MySQL 到 PG 的同步工具还不够成熟,Debezium 算是能用的,但配置起来会让人掉一层皮。如果业务能接受短暂停服,就简单点:凌晨两点开始,先锁表,导数据,校验一致性,再切 DNS。整个过程控制在 10 分钟内比较理想。记得提前准备好回滚方案,万一迁移后性能不达标,能快速切回 MySQL。别想着一次搞定所有事情,数据库迁移本身就是系统工程,需要耐心,也需要预案。


