最近这半年,数据库选型又成了团队里讨论最多的话题。以前大家聊到关系型数据库,第一反应就是 MySQL,官方文档顺手、中间件成熟、DBA 也好招。但从去年开始,越来越多的新项目、甚至老项目的重构方案里,都直接把 PostgreSQL 定了下来。PostgreSQL 这个词在社区和招聘市场里的热度肉眼可见地在涨,很多从 MySQL 过来的同学第一反应是:这玩意儿真有那么好吗?换库的代价可不小,大厂到底图什么?
这篇文章就围绕“为何大厂纷纷转向 PostgreSQL”这个话题,把从 MySQL 到 PG 的进阶之路完整拆一遍。先聊清楚背后的驱动力,再对比两者核心差异,然后给出实际迁移操作的路线图和避坑指南。如果你正在评估新项目选型,或者手上刚好有 MySQL 迁移 PG 的活儿,这篇文章就是按我实际经验来的,可以直接当参考用。
1. 为什么这几年大厂都在换数据库?
1.1 从“默认 MySQL”到“先看场景”
早期互联网应用选 MySQL,很大程度上是历史惯性。LAMP 架构太普及了,MySQL 作为其中一环,教程多、社区大、云厂商支持好,很多公司从第一天起就把它当默认数据库。直到现在,绝大多数中小型 Web 应用用 MySQL 依然合理,MySQL 8.0 以后的能力也一直在提升。
但“默认”不等于“最优”。过去五六年,业务场景明显变复杂了:数据结构不再是一张张整齐的二维表,JSON 半结构化数据越来越多,分析型查询和 OLTP 混在同一个库里的需求越来越普遍。此时 PostgreSQL 的几个硬实力就开始显山露水:它天生支持 JSONB、数组、范围类型,自带窗口函数和 CTE,还有 PostGIS 这种让它直接变成地理空间数据库的扩展。对于一个技术团队来说,一个库能同时承担传统事务、部分分析、半结构化和空间数据的角色,诱惑力是很大的。
而且 PostgreSQL 并不是一夜爆红的。它在学术圈和企业级应用里默默积累了几十年,事务机制、优化器、扩展性设计都是按教科书级别做的。很多人第一次接触 PG 是在读书或开源项目里,等到真正生产环境需要这些能力时,自然就想起它。
1.2 商业驱动的三个现实问题
抛开技术情怀,大厂换数据库最终都是被商业问题逼的。
第一个问题是复杂 SQL 的执行效率。MySQL 的优化器这些年进步不小,但在复杂关联查询、子查询、CTE 递归这类场景下,跟 PostgreSQL 的优化器比还是有差距。PostgreSQL 里有更准确的统计信息机制、更细粒度的代价估算,EXPLAIN 的输出也直观得多。简单说,同样一条多表关联的报表 SQL,在 PG 里经常能跑出比 MySQL 好得多的计划。
第二个问题是数据治理和 SQL 标准合规。很多公司的敏感数据要审计、要统一口径、要支持 BI 工具直接对接。PostgreSQL 对 SQL 标准的支持比 MySQL 严格得多,窗口函数、WITH RECURSIVE、LATERAL JOIN、FILTER 子句这些都直接用,不用再靠奇技淫巧绕过语法限制。做数据平台的同学应该深有感触:从 MySQL 导数据到数仓,SQL 方言差异经常要单独写一层转换逻辑,PG 就能省很多事。
第三个问题是扩展生态。这里不单指 PostGIS 和 pgvector 这种明星扩展,还包括很多小功能:你可以自定义数据类型、自定义聚合函数、甚至自己写一个索引访问方法。PG 允许你在数据库内部“生长”出 MySQL 很难拥有的能力。这些年 AI 应用火起来,pgvector 让 PG 直接成了向量检索的轻量级方案,很多团队根本不需要再单独搭一套向量数据库。这种“一个库解决多个问题”的能力,正是技术团队降本增效最爱看的东西。
1.3 适合谁迁移,不适合谁迁移
我见过不少脑子一热就提迁移方案的,结果迁移完发现团队根本驾驭不了。数据库选型不是换品牌,是换一套能力模型。楼主如果负责评估,下面这两类判断标准很有用。
适合迁移的团队:业务里查询逻辑复杂、需要窗口函数和 CTE 做报表、需要地理空间或全文检索、需要容器化部署和管理成本可控、团队愿意统一一套数据库技术栈;或者是新项目、没有任何存量包袱,那直接用 PG 是很顺理成章的事。
不适合硬迁的团队:业务极其简单、就是几张表的增删改查,MySQL 完全够用;公司 DBA 团队的运维体系全是围绕 MySQL 建的,备份、监控、中间件都成型了,此时要替换意味着运维体系全盘重做;团队没有一个人写过 PG,出了问题连日志都不知道去哪看,那再好的技术也会被落地问题拖死。
换句话说,大厂的转向不是“无脑换”,而是它们原本的复杂需求已经超出了 MySQL 的舒适区。技术选型这事,匹配才是第一位。
2. MySQL 和 PostgreSQL 的核心差异到底在哪里?
2.1 SQL 标准与语法差异
从 MySQL 迁移到 PG,最先感受得到的不是性能,而是 SQL 写法要改。我第一次从 MySQL 迁过去的时候,被 GROUP BY 的报错整得非常狼狈。MySQL 在关闭 ONLY_FULL_GROUP_BY 时允许 SELECT 后面出现非聚合列,PG 则始终严格拒绝。
再比如 INSERT ... ON DUPLICATE KEY UPDATE,MySQL 的经典写法在 PG 里不存在,PG 的替代语法是 INSERT ... ON CONFLICT (唯一键) DO UPDATE SET。功能层面对应,但写法完全不同,改起来真不是单纯换关键字那么简单。
还有字符串函数和聚合函数:MySQL 的 GROUP_CONCAT 对应 PG 的 STRING_AGG;MySQL 的 IFNULL 对应 PG 的 COALESCE;MySQL 的 DATE_FORMAT 对应 PG 的 TO_CHAR。如果不提前把这些差异排查一遍,迁移后第一轮灰度测试就会输在语法兼容上。
下面是我整理的一张高频语法对照表,迁移前建议直接拿去当检查清单:
| 功能描述 | MySQL 写法 | PostgreSQL 写法 |
|---|---|---|
| 冲突时更新 | INSERT ... ON DUPLICATE KEY UPDATE | INSERT ... ON CONFLICT(...) DO UPDATE SET |
| 拼接多行 | GROUP_CONCAT(col SEPARATOR ',') | STRING_AGG(col, ',') |
| 空值替代 | IFNULL(expr, default) | COALESCE(expr, default) |
| 限制返回行数 | LIMIT n OFFSET m | 同样支持 LIMIT/OFFSET |
| 字符串拼接 | CONCAT(a, b) | a || b 或 CONCAT(a, b) |
| 日期格式化 | DATE_FORMAT(d, '%Y-%m-%d') | TO_CHAR(d, 'YYYY-MM-DD') |
| 正则匹配 | REGEXP 'pattern' | ~ 'pattern' 或 ~* 忽略大小写 |
| 布尔字段查询 | WHERE is_active = 1 | WHERE is_active = true |
提个细节:PG 里未加引号的标识符会自动折叠成小写,所以表名要是在 MySQL 里用了大写驼峰,迁移之前最好先统一成小写,否则查询时一会儿成功一会儿报错,排查起来非常折磨。
2.2 数据类型与存储机制对比
第二个绕不开的是数据类型映射。很多看起来“差不多”的类型,实际语义差得很远。MySQL 里最常用的 TINYINT(1) 通常被当作布尔用,而 PG 里有真正的 BOOLEAN 类型;MySQL 的 DATETIME 不带时区,PG 推荐用 TIMESTAMPTZ 来避免时区换算出幺蛾子。
AUTO_INCREMENT 也得重点说。MySQL 的自增主键是表属性,而 PG 里常见的是序列加默认值。旧写法用 SERIAL 伪类型,PG 10 之后更推荐 GENERATED AS IDENTITY,语义上更标准。很多人没注意的一个坑是:MySQL 里执行 INSERT 失败后 AUTO_INCREMENT 的值也会消耗掉,PG 的序列同样有这个问题,但 PG 里序列的行为更可控,你可以用 SETVAL 重新调整序列值,这在做数据修复时非常好用。
还有个经常被忽略的差异在字符串类型。MySQL 的 VARCHAR(50) 表示最多 50 个字符,PG 里同样成立。但 MySQL 旧版里的 TEXT 类型不能有默认值,PG 的 TEXT 和 VARCHAR 基本通用,没有固定长度限制,反而更省心。另外 PG 里 CHAR(n) 在存储时会填充空格,查出来还要你去 TRIM,实际开发中大家基本只用 VARCHAR 或 TEXT。
JSON 类型值得单独拎出来讲。MySQL 8.0 的 JSON 是二进制存储,已经很好用了;但 PG 的 JSONB 更激进——它把 JSON 解析成二进制格式,支持 GIN 索引,可以在 JSON 内部的 key 上建索引。这在很多“半结构化业务”场景里是降维打击。你不需要专门上一个文档数据库,直接在关系库里就能把 JSON 查得很顺手。
2.3 索引与查询优化器的差异
索引这块,MySQL 的主力是 B+Tree,PG 的主力也是 B-Tree,但 PG 的扩展性要强很多。
说说 PG 的 GIN 索引。它专为数组、JSONB 和全文检索设计,比如你有一列 tags text[],想查包含“postgres”的标签记录,建一个 GIN 索引后查询速度可以提升一到两个数量级。还有 BRIN 索引,适合海量有序数据(比如日志表),它只存储块范围的信息,索引体积非常小,对超大表的查询性能提升也很明显。
除此之外,PG 还支持 Partial Index、Expression Index。LOWER(email) 上建索引、只对 status='active' 的行建索引,这些都是 MySQL 语法层面想都不敢想的操作。对大厂来说,这些索引特性意味着能直接解决慢查询问题,而不需要额外引入搜索引擎或数仓。
在查询优化器方面,PG 的 EXPLAIN ANALYZE 输出真的非常适合逐行阅读,它会告诉你每个节点的代价、循环次数、实际行数,你基本能顺着输出定位到瓶颈是索引缺失、统计信息过期还是关联顺序错乱。MySQL EXPLAIN 虽然也够用,但信息量没有这么细致。配合 pg_stat_statements 插件做慢查询分析,基本上排查 SQL 问题就是“现代战争”了。
3. 从 MySQL 到 PostgreSQL 的迁移实操路线
3.1 迁移前评估与版本选择
迁移不是下载个安装包把数据倒过去就完事。我见过太多失败案例,都是因为前期评估不到位,上线后才发现 SQL 不兼容、字符集乱码、时区错乱,最后只能回滚,搞得团队半个月都在填坑。
你至少要做四件事:
- 盘点存量对象:所有表、视图、存储过程、触发器、事件、定时任务,列一个完整清单。
- 检查 SQL 兼容性:把应用里的 SQL 全部收集出来,跑一遍语法扫描。常用工具比如 pgloader 有 dry-run 模式,或者用 EXPLAIN 在 PG 里挨个跑,看有没有语法报错。
- 确认字符集与排序规则:MySQL 里常见的 utf8mb4 在 PG 里就是 UTF8,但要注意排序规则可能不同,尤其是中文排序,建议迁移后统一用 en_US.UTF-8 或 C.UTF-8,避免排列顺序和应用预期不一致。
- 明确版本策略:PostgreSQL 版本迭代很快,14、15、16、17 各有提升,但生产环境我一般建议选当前稳定版的前一两个版本,比如 PG 16 或 15。太新的版本社区反馈数据少,踩坑了都没地方问。
版本选择这个话题,云托管和自己部署是两个思路。云托管(比如 RDS for PostgreSQL)省心,升级和备份都有人管;自建则要自己处理流复制、突发故障、磁盘空间等问题。从 MySQL 过来的团队,我建议先走云托管,等团队熟悉了再考虑自建。除非公司已经有很成熟的 PG 运维体系,否则刚开始就自建 PG 集群,运维压力会非常大。
3.2 全量迁移:pgloader 与 mysqldump 双方案
全量迁移一般两条路:一条是工具直连转换,一条是文件转储导入。我平时最推荐的是 pgloader,因为是专门为这类迁移设计的,能自动处理类型映射和常用语法转换。
先看 pgloader 怎么用。假设 MySQL 在 127.0.0.1:3306,数据库名 app_db,目标 PG 在 192.168.1.10:5432,数据库名 app_target,在服务器上创建一个 load.load 文件:
LOAD DATABASE FROM mysql://app_user:app_pass@127.0.0.1:3306/app_db INTO postgresql://pg_user:pg_pass@192.168.1.10:5432/app_target WITH include drop, create tables, create indexes, reset sequences, data only CAST type datetime to timestamptz drop default drop not null using zero-dates-to-null;然后执行:
pgloader load.load这个过程会自动建表、迁移数据、创建索引,并在迁移结束后输出一份详细报告,告诉你哪些表成功、哪些字段做了类型转换、有多少行报错。我建议你第一次执行时先不要带 “data only”,确保表结构生成正确后再导数据,分步走更容易定位问题。
如果你的网络环境访问不到源库,或者 DBA 不允许直连,那可以走文件导出路线。MySQL 侧使用 mysqldump 导出兼容格式:
mysqldump -h 127.0.0.1 -u app_user -p --compatible=postgresql \ --default-character-set=utf8mb4 --no-autocommit app_db > app_db.sql不过说实话,mysqldump 的 --compatible=postgresql 只能处理一小部分语法,类型转换还是得人工处理。导出的文件拿到 PG 里执行前,你需要先手工改掉自增列、布尔字段、JSON 类型这些差异。所以我更推荐用小表数据用工具直迁、大表提前分批导的策略:结构用 pgloader 建,数据用 mysqldump 导出成 CSV,再用 PG 的 COPY 命令批量导入。COPY 在大数据量下的效率非常猛,比一行一行 INSERT 快一个数量级。
下面是我常用的手动类型映射参考表:
| MySQL 类型 | PostgreSQL 类型 | 迁移备注 |
|---|---|---|
| TINYINT(1) | BOOLEAN | 0 转 false,1 转 true |
| TINYINT / SMALLINT | SMALLINT | 仅剩少数字节长度,不影响 |
| INT | INTEGER | 可直接对应 |
| BIGINT | BIGINT | 可直接对应 |
| VARCHAR(n) | VARCHAR(n) | 长度一致时可直接对应 |
| TEXT / LONGTEXT | TEXT | 无长度限制 |
| DATETIME | TIMESTAMP / TIMESTAMPTZ | 推荐 TIMESTAMPTZ,注意时区 |
| TIMESTAMP | TIMESTAMPTZ | 别忽略时区 |
| JSON | JSONB | 推荐使用 JSONB,方便索引 |
| ENUM | VARCHAR + CHECK | PG 也有 ENUM,但尽量少用 |
| BLOB | BYTEA | 二进制类型对应 |
给个实战例子吧,MySQL 里的建表语句是:
CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, profile JSON );迁移到 PG 里应该写成:
CREATE TABLE users ( id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(50) NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, profile JSONB );这里有个心理预期要提前建立:迁移不是一次性的“数据搬家”,而是重新整理数据结构的机会。很多团队在迁移时顺手把不规范的类型全部纠正过来,效果远好于机械复制。
3.3 增量同步与并行切换
如果你不能接受停机迁移,那就需要增量同步。PG 生态里最正统的方案是逻辑复制:源端 MySQL 开启 binlog,中间用 Debezium 或者 Flink CDC 把 binlog 事件解析出来,再通过 PG 的逻辑复制或直接写入目标库,实现两个库的准实时同步。
这里要注意,PG 的逻辑复制在 PG 10 后才正式成熟,发布端和订阅端版本最好都比较新。操作大致是:
- 在 MySQL 侧开启 binlog,确保 binlog_format=ROW。
- 部署一个 Debezium Connector,把 MySQL 数据变更推送到 Kafka。
- 下游连到目标 PG,写入变更。
- 切流之前做全量一致性校验,确认数据一致后再把应用连接从 MySQL 切换到 PG。
我实际操作中,增量同步最难的不是同步本身,而是数据一致性校验。两边库的哈希值没法直接比对,尤其是 JSON、浮点、时区字段,很容易出现“看起来一样,实际二进制不同”的情况。建议校验工具自己做:每张表按主键分批读取,计算行数、聚合 checksum,不一致的地方再细查差异行。别怕麻烦,这个环节越严格,切换后的问题越少。
切换策略上,我强烈建议做“灰度切换”。先切只读流量,让一部分查询走 PG;跑一周确认没有性能问题,再切写流量。有些团队上来就全量切,出了问题直接原地爆炸,这其实是项目管理问题,不是技术问题。
4. 迁移后容易踩的 8 个坑与排查技巧
4.1 连接与部署类问题
第一个坑是连接数。MySQL 的连接数因为有中间层,通常不是问题;PG 默认的 max_connections 是 100,生产环境必须调大,但调大之后内存占用也会变高。每个连接在 PG 里都可能分配 work_mem,你要是把连接数拉到 1000,内存直接爆掉。正确做法是应用层用连接池,比如 PgBouncer,后端保持稳定数量的长连接。
第二个坑是 pg_hba.conf。PG 默认只允许本地连接,你从应用服务器远程连不上时,第一个想到的就是去检查 pg_hba.conf 的 host 规则,而不是怀疑防火墙。很多新手折腾半天,最后只是这条配置没改。
下面是常见错误速查表,建议收藏:
| 错误信息 | 原因 | 排查方向 |
|---|---|---|
| FATAL: password authentication failed | 密码错误或 pg_hba.conf 认证方式冲突 | 先确认 pg_hba.conf 是否用的 scram-sha-256 / md5 |
| could not connect to server: Connection refused | PG 未启动、端口不对或防火墙拦截 | 检查 pg_isready,确认端口 5432 是否监听 |
| sorry, too many clients already | 连接数达到 max_connections | 顺手查一下 pg_stat_activity,看活跃连接分布 |
| must be superuser to create extension | 扩展安装权限不足 | 用超级用户执行 CREATE EXTENSION |
| permission denied for schema public | 用户未授权 | 检查用户权限 GRANT USAGE ON SCHEMA ... |
| duplicate key value violates unique constraint | 序列值落后于表内最大值 | 执行 SELECT setval(..., max(id)) |
| value too long for type character varying(n) | 数据超过 VARCHAR 长度 | 与 MySQL 的宽松截断行为完全不同,PG 直接报错 |
第三个坑是序列值滞后。因为迁移数据时用了 COPY,表的 id 序列不会自动更新,结果应用插入新数据时说主键冲突。这个几乎是每个迁移团队都会踩的坑,操作其实很简单,对每张表执行一次 setval 同步序列到当前最大值即可:
SELECT setval(pg_get_serial_sequence('users', 'id'), (SELECT COALESCE(MAX(id), 1) FROM users));4.2 数据行为差异类问题
第一个大差异是 NULL 排序。MySQL 里默认升序排序时 NULL 排最前,PG 里默认 NULL 排在最后。对于分页报表,这个差异很容易导致数据顺序和之前完全不一致。解决办法是按业务需求显式加上 NULLS FIRST / NULLS LAST。
第二个大差异是 GROUP BY 的严格模式,前面已经说过了。还有一个小兄弟是 ONLY_FULL_GROUP_BY 造成的“看似能跑、跑了结果是错的”这类隐形问题,在 MySQL 宽松模式下能过,在 PG 里直接报错。从另一个角度想,这种严格性反而是好事——它强迫你把 SQL 写规范。
第三个是时区。MySQL 的 DATETIME 不带时区,迁移到 PG 如果用 TIMESTAMPTZ,显示出来会跟着服务器时区走,应用层拿到的时间数字可能跟以前不一样。排查法很简单:统一用 UTC 存数据,应用层统一转本地时间。如果不想动太多代码,迁移初期也可以用 TIMESTAMP 类型避开时区差异,但长期看还是统一 TIMESTAMPTZ 更规范。
第四个坑是 Boolean 和整数混用。MySQL 里你可以写 WHERE is_active=1,PG 里这写会报错:operator does not exist: boolean = integer。看起来是小事,但如果业务代码里到处是这种写法,迁移工作量大到你怀疑人生。最好写个简单的 SQL 扫描脚本,把应用中涉及布尔字段的查询全部揪出来。
4.3 SQL 写法兼容类问题
除了前面语法对照表里提到的,还有两个高频问题值得注意。
第一个是 REGEXP 和正则表达式风格。MySQL 的 REGEXP 用的是 POSIX 风格简化方言,PG 的 ~ 操作符用的是 POSIX 标准正则,功能更强,但表达式写法可能不完全兼容。比如 MySQL 里简单的 LIKE '%abc%' 迁移后可以用 LIKE 或 POSITION,但复杂正则建议逐条重写并做好离线回归测试。
第二个是存储过程的差异。MySQL 的存储过程和函数用 BEGIN...END 包起来,变量语法是 DECLARE + SET;PG 的 PL/pgSQL 则用 $$ ... $$ 结构,变量用 := 赋值。迁移时不要想着自动转换,几乎不可能,手写重写反而最省时间。除非你的项目里存储过程特别多,那迁移成本会很高,评估时需要单独列出来。
4.4 出问题时怎么排查比较高效
迁移上线前两周,大概率是问题高发期。我的排查顺序是:
- 先看 pg_stat_activity,查有没有锁等待或长时间运行的事务。
- 打开慢查询日志,或者用 pg_stat_statements 按执行时间排序,把 TOP 20 SQL 揪出来。
- 对每条慢 SQL 执行 EXPLAIN (ANALYZE, BUFFERS),看预计行数和实际行数偏差大不大。如果偏差大,说明统计信息没过新,要跑一次 ANALYZE。
- 检查 VACUUM 是不是堵住了。PG 的 MVCC 依赖 VACUUM 清理死行,如果 autovacuum 没有正常运行,表会无限膨胀,查询性能会肉眼可见地下降。
这里补一句关于 VACUUM 的,很多人从 MySQL 过来容易忽略。MySQL 的 InnoDB 有后台 purge 线程自动清理,PG 也自动,但不够及时。对频繁更新的大表,要关注表的 bloat 情况。必要时手动执行 VACUUM (ANALYZE) 或 VACUUM FULL,后者会锁表,只能趁维护窗口做。
最后,一定要在迁移后跑一周以上的并行对比。两个库同时写入,每天对比核心业务数据是否一致,应用层流量逐步放大。这个过程虽然拉长了项目周期,但能把风险分散到最小。
5. 迁移后的优化与长期运维建议
5.1 基础参数调优
PG 默认配置很保守,刚安装完就直接上生产的话,性能很难看。和 MySQL 类似,调参的核心是内存。下面是我经过多次压测后的起步参数,你可以根据自己的机器情况调整:
# postgresql.conf 关键参数 shared_buffers = 4GB # 建议为物理内存的 1/4 effective_cache_size = 12GB # 建议为物理内存的 3/4 work_mem = 64MB # 排序/哈希操作内存,按需调整 maintenance_work_mem = 1GB # VACUUM、CREATE INDEX 用 checkpoint_completion_target = 0.9 max_connections = 300 # 实际按业务定 random_page_cost = 1.1 # SSD 环境建议调低要注意的是:work_mem 不是越大越好。它是每个查询执行节点都可能分配的内存,连接数多了以后,总内存消耗会爆炸。压测时观察峰值内存,再逐步调。
5.2 索引与 Vacuum 的日常功课
PG 里索引不是越多越好。MySQL 里冗余索引会影响写入性能,PG 也一样。但 PG 的索引类型多,要按查询模式选:精确匹配走普通 B-Tree,JSONB 和数组查询走 GIN,日志型大表走 BRIN。索引设计做完之后,别忘了定期跑 VACUUM,维护统计信息和释放空间。
5.3 要不要做读写分离
MySQL 场景里读写分离是个常见架构,通常靠中间件或主从复制实现。PG 同样支持物理流复制和逻辑复制,也有 Patroni 这类高可用方案。但我建议你先别急着搭集群。单机 PG 在绝大多数业务里性能都够,先把单机跑稳、参数调对、索引建好,比盲目搞一堆副本实际得多。等业务量确实上来了,再用 Patroni + etcd 做高可用,复杂度可控。
最后说一点我自己的体会。带团队从 MySQL 迁到 PG,最困难的根本不是 SQL 语法差异,而是调整认知惯性。很多人在 MySQL 里习惯性用“绕过问题”的方式写 SQL,换到 PG 里,很多问题可以直接用正规功能解决。PostgreSQL 的能力很强,但它不会替你决定怎么做数据模型,它只是给了你更多正确做事的可能性。如果你愿意静下心读一遍官方文档,再把习惯性思维调整过来,这场迁移带来的收益,远不止“换了数据库”这么简单。