大家好,我是数据库小学妹👋我踩过的坑,你别再踩。
“PostgreSQL vs MySQL,2026年到底该学哪个?”这个问题我回答过不下20遍。以我个人来说,转行时先学的MySQL,后来在生产环境同时维护MySQL和PG两套系统,两边的坑都踩过。今天把真实对比写出来,不站队,只讲技术差异和适用场景,希望能给迷茫的你一点思路,少走弯路少踩坑。
先交代版本背景。MySQL 8.0已经在2026年4月结束生命周期,9.0已发布,引入原生VECTOR向量类型和向量检索,9.7.0 LTS是下一代长期支持版。PG这边主流是17.x。下面的对比以这两个最新版本为准。
架构设计的根本分歧
MySQL和PG最大的区别不在SQL语法,在架构哲学。MySQL是多引擎架构,核心是Server层加存储引擎层,Server层处理连接、解析、优化,存储引擎负责数据存储和事务。InnoDB是最常用的引擎,但你也可以换成MyISAM、Memory、TokuDB。PG是单引擎架构,整个数据库是一个统一系统,存储引擎就是内核本身,你不能换引擎,但可以扩展类型系统、加索引类型、写自定义函数。
MySQL架构:
Client → Server层(解析/优化) → InnoDB引擎(存储/事务)
PG架构:
Client → 内核(解析/优化/存储/事务一体化)
这个差异决定了后续所有不同。还有一个连带差异在连接模型上:MySQL默认一个连接一个线程,线程轻量但共享进程内存;PG一个连接一个进程,进程隔离更彻底,但连接开销大,高并发下通常要靠连接池顶住。
索引实现:MySQL的B+Tree vs PG的多索引类型
MySQL InnoDB只有一种主索引B+Tree,所有查询优化都围绕它展开。PG支持六种索引类型,每种针对不同场景。
| 索引类型 | 适用场景 | 查询算子 |
|---|---|---|
| B-Tree | 等值、范围、排序 | = > < >= <= BETWEEN |
| Hash | 纯等值查询 | = |
| GiST | 范围类型、几何数据 | 自定义 |
| GIN | 全文检索、数组、JSONB | @> && |
| SP-GiST | 空间分区树 | 自定义 |
| BRIN | 超大表块级索引 | 范围过滤 |
生产环境最常用的是B-Tree和GIN。GIN是PG的杀手级特性,本质是倒排索引,JSONB字段用它就能高效查询嵌套文档的任意路径。
-- PG: JSONB GIN索引
CREATE INDEX idx_user_profile ON t_user USING GIN(profile);
-- 高效查询嵌套字段
SELECT * FROM t_user
WHERE profile @> '{"vip": true, "level": 3}';
MySQL 8.0也支持JSON,但查询嵌套字段主要靠生成列加索引,8.0.17后才多了多值索引支持JSON数组,整体灵活性和效率还是比PG的GIN差一个量级。
一个真实的性能差距
同样一个用户画像表,五百万行,每个用户三十个标签,按标签组合查询。同样的查询,PG用JSONB+GIN索引,MySQL用生成列加B+Tree,复杂嵌套查询上PG明显更快,响应差距肉眼可见。具体差多少倍我不敢给你精确数字,这个受表结构、数据分布、硬件影响太大。更宏观的性能面,2026年3月的权威OLTP基准里,PostgreSQL 17.9在读写混合TPS上约是MySQL 8.4.8的六倍多,点查QPS也领先,MySQL只在纯读点查上略占优。
事务隔离:RR和RC的不同实现
两个数据库都支持四种隔离级别,但默认级别不同:MySQL默认Repeatable Read,PG默认Read Committed。这不是随意选的,是MVCC实现方式不同决定的。
MySQL的MVCC:基于undo log
MySQL的InnoDB在更新数据时,原值写入undo log,新值写入数据页。读操作读的是数据页加undo log重建的快照版本,每个事务有自己的读视图。RR级别下同一个事务内的多次读取看到同一个快照,天然解决不可重复读,再加上Next-Key Lock,绝大多数场景也杜绝了幻读。它和PG的区别只是实现方式不同:MySQL靠加锁,PG靠快照,不是说MySQL的RR有功能缺陷。
PG的MVCC:基于多版本行
PG的更新不修改原行,而是写入新行,旧行标记为过期。读操作直接读数据页里的可见版本,不需要undo log。RC级别下每次SELECT看到查询开始时的最新快照,同一个事务内两次SELECT可能看到不同结果。所以PG的默认RC比MySQL的RR弱一级,但并发性能更好。
隔离级别对比
| 隔离级别 | MySQL | PG |
|---|---|---|
| Read Uncommitted | 支持 | 支持(等同于RC) |
| Read Committed | 支持 | 默认 |
| Repeatable Read | 默认 | 支持 |
| Serializable | 支持 | 支持(SSI) |
这张表容易让人误会,其实两个数据库都完整支持所有四种隔离级别,区别只是默认值不同:MySQL默认RR,PG默认RC。
PG的Serializable用的是SSI可串行化隔离,比MySQL的snapshot serializable更强,能检测到写偏斜这类真正的序列化冲突。但SSI的性能开销很大,生产环境很少真正用它。
选型建议
读多写少、一致性要求高的场景,MySQL的RR默认级别开箱即用。高并发写入、读一致性要求不极端的场景,PG的RC默认性能更好。金融级强一致场景,PG的SSI更可靠,但性能开销大,实际业务里很少用。
复制机制:binlog+SQL线程 vs 流复制WAL
MySQL的主从复制是异步的,主库写binlog,从库的IO线程拉取binlog写到relay log,SQL线程重放relay log。
Master → binlog → Slave IO Thread → relay log → Slave SQL Thread → 重放
延迟是MySQL主从的固有缺陷,大事务、DDL、单线程重放都会导致延迟。8.0引入了并行复制,早期并行度受限于跨库事务,8.0.26后引入了基于WRITESET的并行复制,并行度不再只限于跨库,实际效果取决于配置。
PG的流复制更底层,主库写WAL预写日志,从库直接通过网络接收WAL流,在存储引擎层重放。从库可以设为热备模式,重放的同时接受只读查询。PG 17还新增了从备库进行逻辑复制的能力和pg_wait_until_replay_caughtup()函数,逻辑复制进一步增强。
Primary → WAL Sender → WAL Stream → Standby WAL Receiver → 引擎层重放
延迟对比
MySQL主从延迟在生产环境常见3到10秒,大事务场景可能到分钟级。PG流复制延迟通常在毫秒级,同城部署基本感知不到。
故障切换
MySQL需要第三方工具,MHA、Orchestrator、MGR。PG自身不带自动故障切换,通常用repmgr、Patroni或pg_auto_failover来做,Patroni基于etcd或ZooKeeper做选主,是目前的主流选择。但生态成熟度上,MySQL的高可用方案更丰富,ProxySQL、MHA、Orchestrator都有大量生产验证。
运维体验:谁让你少加班
这部分是纯个人感受,两个库我都维护过生产环境。
日常操作对比
| 操作 | MySQL | PG |
|---|---|---|
| 安装 | yum/apt一条命令 | 官方源配置稍复杂 |
| 客户端 | mysql命令行 | psql功能更强 |
| 查看连接 | SHOW PROCESSLIST | pg_stat_activity视图 |
| 分析查询 | EXPLAIN | EXPLAIN ANALYZE BUFFERS |
| 备份 | mysqldump / xtrabackup | pg_dump / pg_basebackup |
| 在线DDL | 加列等走INSTANT秒完成,改类型仍需pt-osc/gh-ost | 多数DDL不阻塞读写 |
| VACUUM | 不需要 | 必须配置自动VACUUM |
补充一句在线DDL:MySQL 8.0的INSTANT加列不是万能的,行格式为COMPRESSED的表、部分字段类型都有限制,不是所有加列都能走INSTANT。
PG的EXPLAIN ANALYZE BUFFERS是我最喜欢的功能,直接显示实际执行时间、实际扫描行数、buffer命中情况。MySQL的EXPLAIN默认只有估算值,不过8.0也支持EXPLAIN ANALYZE,能看实际执行时间和行数,只是不如PG的EXPLAIN (ANALYZE, BUFFERS)详细。
-- PG: 真实的执行统计
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM t_user WHERE status = 1;
-- 输出包含:
-- 实际执行时间: 12.345ms
-- 实际行数: 5230
-- Buffer命中: shared hit=1024 read=38
-- MySQL: 只有估算值
EXPLAIN SELECT * FROM t_user WHERE status = 1;
-- rows列是优化器估算值,可能与实际差几倍
VACUUM问题
PG的UPDATE不删除原行,旧行标记为死行,长期不VACUUM表会膨胀。我遇到过一张表,实际数据五万行,死行五十万行,表文件占了8G。
-- PG: 查看死行
SELECT relname, n_live_tup, n_dead_tup,
ROUND(n_dead_tup::numeric / NULLIF(n_live_tup, 0) * 100, 1) AS dead_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 0
ORDER BY n_dead_tup DESC;
自动VACUUM必须开,但默认阈值对大表不够激进。好消息是PG 17在VACUUM上有明显改进,内存消耗降低,高并发写入性能翻倍,老版本需要更激进调参,17之后默认行为已经好很多。
# postgresql.conf
autovacuum = on
# 默认20%,对大表太宽松
autovacuum_vacuum_scale_factor = 0.05
# 对热点表单独设
ALTER TABLE t_user SET (autovacuum_vacuum_scale_factor = 0.01);
MySQL不需要操心这个问题,InnoDB的MVCC基于undo log,没有死行概念。
扩展性
PG的类型系统和函数系统远超MySQL,自定义类型、数组类型、范围类型、复合类型都有。扩展生态也很强,pgvector做向量检索,PostGIS做地理信息,timescaledb做时序数据。MySQL的扩展点主要在存储引擎层,用户侧能做的扩展有限,不过这个短板正在补,MySQL 9.0已经内置原生VECTOR向量类型和向量检索,虽然还在早期阶段。
生态和社区
MySQL的生态优势在于规模,互联网公司用MySQL的最多,招聘市场上MySQL DBA岗位是PG的五倍以上,教程、工具、第三方服务都是MySQL优先。PG的优势在于技术深度,复杂查询、GIS、数据分析、JSON处理领先,开发者社区对PG的评价普遍更高。
我团队里三个DBA,两个MySQL熟,一个PG熟。MySQL出线上问题,随便一个DBA都能处理;PG出了问题,只有那个PG熟的人能搞定。这就是MySQL生态优势的现实体现。
选型决策框架
最后给一个实用的决策路径。互联网业务后端,高并发读写、简单查询为主、团队MySQL经验多,选MySQL。复杂查询、GIS、JSON处理、数据分析,需要强一致性和高级类型,选PG。传统企业转型,已有Oracle系统要迁移,需要存储过程和复杂业务逻辑,PG兼容性更好。如果两个都要,读写分离架构里MySQL做写入、PG做分析,也是常见方案。
避坑清单
PG的UPDATE不删除旧行,而是写入新行、把旧行标记为死行,长期不VACUUM表文件会严重膨胀。我遇到过一张表,实际数据五万行、死行五十万行,表文件占了8G。autovacuum必须开,但默认20%的阈值对大表太宽松,要调低scale_factor,热点表单独设更小的值。
MySQL默认EXPLAIN里的rows列是优化器估算值,可能和实际差几倍,别拿它当真。8.0虽然支持EXPLAIN ANALYZE,但调优还是要结合慢日志里的实际扫描行数和执行时间验证,判断走没走索引最终还是要实测。
PG的work_mem是每个排序、哈希操作单独分配的内存上限,不是全局共享。连接数多时,真实内存消耗是连接数乘以work_mem,几百个连接可能直接把内存打爆。OLAP场景想调大work_mem避免落盘,得先盯住连接池规模。
两个数据库的分页都用LIMIT/OFFSET,深分页性能都差,因为要扫描前面所有行再丢弃。百万级偏移别再用OFFSET,改成游标分页,用上一页最后一条的主键当游标继续往后查。
总结
MySQL和PG没有绝对的谁更好,只有谁更适合。MySQL赢在生态规模、开箱即用和运维人才储备,PG赢在技术深度、类型系统和一致性能力。如果团队以MySQL为主,别急着全面上PG,先把JSON、复杂查询这类场景单独拆出去试;如果团队已经是PG,就发挥它的一致性优势,把SSI和高级类型用起来。选型最怕的是用A的思维硬套B。
我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋