之前一直用 MySQL,后来做了个项目需要处理地理数据和复杂查询,听说 PostgreSQL 这方面更顺手,就动了迁移的心思。迁移过程比我想的折腾不少。
第一个碰到的是自增主键。MySQL 写惯了:
id INT AUTO_INCREMENT PRIMARY KEY
拿到 PG 里直接报错。PG 用 SERIAL:
id SERIAL PRIMARY KEY
或者标准写法 GENERATED AS IDENTITY。SERIAL 背后会自动建一个序列对象,GENERATED AS IDENTITY 更严格一点,PG 会完全接管序列,不让你手动插入冲突值。
翻页也有区别。MySQL 的写法:
SELECT * FROM users LIMIT 10 OFFSET 20;
PG 也吃这套,但它还支持 SQL 标准的写法:
SELECT * FROM users OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
PG 13 以下有个坑——FETCH NEXT 后面不能跟参数,只能写固定数字,后来版本才修了。
字符串拼接一开始也让我懵了一会儿。MySQL 用 CONCAT():
SELECT CONCAT(first_name, ' ', last_name);
PG 用 ||:
SELECT first_name || ' ' || last_name;
在 MySQL 里 || 是逻辑或,第一次在 PG 里写 SELECT 1 || 2 出了个完全不一样的结果,查了半天才反应过来。
全文检索这块,PG 确实更强。MySQL 的全文检索只支持 MyISAM 和 InnoDB,中文分词基本靠第三方。PG 原生支持 tsvector 和 tsquery:
SELECT * FROM articles
WHERE to_tsvector('simple', title) @@ to_tsquery('simple', 'postgresql & migration');
为了中文分词我还配了 SCWS 插件,配置过程有点折腾,但效果比 MySQL 好太多。
GROUP BY 的严格模式也坑了我一次。MySQL 允许 SELECT 里出现不在 GROUP BY 中的非聚合列:
SELECT id, name, COUNT(*) FROM orders GROUP BY status;
PG 直接报错,要求 SELECT 里的非聚合列必须都在 GROUP BY 里。要么把 id 和 name 加进去,要么用 MAX() 之类的聚合函数包一下。这个习惯改过来之后,SQL 写得更严谨了。
备份工具也不太一样。MySQL 用 mysqldump,PG 对应用 pg_dump:
pg_dump -U postgres dbname > backup.sql
pg_dump 支持更多格式:纯 SQL、tar、自定义压缩。自定义格式可以用 pg_restore 选择性恢复某张表,这在 MySQL 里不太方便:
pg_dump -U postgres -Fc dbname > backup.dump
pg_restore -U postgres -d dbname -t users backup.dump
而且 pg_restore 可以指定并行度,大库恢复快很多。
从 MySQL 迁移到 PG,初期确实不习惯,语法和工具链差异不小。但用了一段时间之后,PG 的标准化程度和扩展能力(JSONB、全文检索、GIS)确实值回迁移成本。我前后折腾了两周才把所有业务流程跑顺,现在用着挺顺手。
评论一下?