前言
如果你在2010年问一个开发者选什么数据库,答案几乎是条件反射式的:"MySQL啊,还用问?"
但如果你在2025年问同样的问题,答案变得有趣了起来。越来越多的新项目在启动时选择了PostgreSQL,而MySQL似乎成了"历史遗留系统"的代名词。
这不是偶然。让我们从技术演进的角度,聊聊这场"数据库王座"的更替。
历史轨迹:两条不同的路
两个数据库诞生时间相近,但走了完全不同的路:
- MySQL:选择了"够用就好",在 Web 1.0/2.0 时代凭借简单易用快速占领市场
- PostgreSQL:选择了"做正确的事",早期被批评"过度设计",但这些设计后来都成了护城河
技术对比:细节里的魔鬼
事务与一致性
这是最核心的差异。支付、金融这类对一致性要求极高的场景,PostgreSQL 的事务模型显著更可靠:
| 特性 | MySQL (InnoDB) | PostgreSQL |
|---|---|---|
| 默认隔离级别 | REPEATABLE READ | READ COMMITTED |
| SERIALIZABLE 实现 | 基于锁,性能差 | SSI(串行化快照隔离),性能好 |
| MVCC 实现 | Undo Log,长事务导致膨胀 | 行内版本链,更优雅 |
| 间隙锁 | 有,容易造成意外锁等待 | 无,锁行为可预测 |
| DDL 事务 | ❌ 不支持 | ✅ 支持 |
实际案例:MySQL 的间隙锁在高并发写入时经常造成"明明只想锁一行,结果锁了一片"的问题。这在支付扣款场景下是灾难。
深入 MVCC:同名不同命
MVCC(多版本并发控制)是现代数据库的灵魂。两者都号称支持 MVCC,但实现方式截然不同——这直接决定了高负载下的表现。
MySQL (InnoDB) 的 Undo Log 回溯模型:
InnoDB 的数据页只保留最新版本的行数据,旧版本被推入 Undo Log 链表。读操作需要沿链表回溯,直到找到对当前事务可见的版本。短事务场景下没问题,但一旦出现长事务,噩梦就开始了:
真实踩坑场景:某支付系统中,一个忘记提交的统计查询事务存活了 2 小时,导致 Undo tablespace 膨胀到 50GB,整个库的写入性能下降 60%。DBA 半夜被叫起来 kill 掉事务后,purge 线程又花了 40 分钟清理 Undo Log。
PostgreSQL 的 Heap 内多版本模型:
V2 (xmin=200, xmax=300)
V3 (xmin=300, xmax=∞) Heap-->>TX_B: 根据快照判断 V2 可见 TX_B->>TX_B: ✅ 返回 V2
PostgreSQL 的做法截然不同——新旧版本都存储在数据页(Heap)内,每行通过 xmin/xmax 标记创建和删除的事务 ID,通过 Visibility Map 快速跳过"全部可见"的页面。HOT(Heap Only Tuple)优化让不涉及索引列的更新直接在同一页内完成,避免索引维护。
代价是什么?旧版本不会自动消失,需要 VACUUM 来清理死元组。这是 PostgreSQL 运维的核心关注点。
两种模型的核心权衡:
Table Bloat] PC --> PD[❌ 需要 VACUUM 维护] PD --> PE[✅ autovacuum 自动化] end style IE fill:#ffcccc style PE fill:#ccffcc
| 维度 | MySQL (InnoDB) | PostgreSQL |
|---|---|---|
| 旧版本存储位置 | Undo Tablespace(独立区域) | Heap 数据页(就地存储) |
| 读旧版本开销 | 需回溯 Undo 链,链长则慢 | 直接在页内扫描,开销稳定 |
| 膨胀风险 | Undo tablespace 膨胀 | 表/索引膨胀(Table Bloat) |
| 清理机制 | Purge 后台线程 | VACUUM(autovacuum 自动触发) |
| 长事务影响 | 灾难性:阻塞 Undo 回收 | 可控:autovacuum 仍可工作 |
| HOT 优化 | 无 | 有,减少索引维护开销 |
一句话总结:MySQL 把复杂性藏在了 Undo Log 里,平时看不见,出问题时抓瞎;PostgreSQL 把复杂性摆在了台面上(VACUUM),逼你正视它,但行为更可预测。
数据类型与表达能力
PostgreSQL 对复杂数据结构的原生支持,让很多场景下的表设计更自然:
-- PostgreSQL:存储用户标签,原生数组
CREATE TABLE users (
id SERIAL PRIMARY KEY,
tags TEXT[], -- 原生数组类型
metadata JSONB, -- 二进制JSON,可索引
ip_address INET, -- 原生IP类型
valid_period TSRANGE -- 时间范围类型
);
-- 查询:找包含某标签的用户
SELECT * FROM users WHERE 'vip' = ANY(tags);
-- MySQL:只能用 JSON 或关联表模拟
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
tags JSON, -- 文本存储,查询慢
metadata JSON,
ip_address VARCHAR(45),
valid_from DATETIME,
valid_to DATETIME
);
-- 查询:需要 JSON 函数,性能差
SELECT * FROM users WHERE JSON_CONTAINS(tags, '"vip"');深入 JSONB:不只是"都支持 JSON"
虽然 MySQL 和 PostgreSQL 都支持 JSON,但底层实现完全不同——这个差异大到值得单独拎出来说。
需遍历整个文档"] MJ3 --> MJ4["索引: 需创建虚拟生成列
再在生成列上建索引"] end subgraph PG_JSONB["PostgreSQL JSONB"] PJ1["INSERT: 解析为二进制格式"] --> PJ2["SELECT: 直接读取二进制"] PJ2 --> PJ3["查询字段: 二进制偏移
O(1) 直达目标"] PJ3 --> PJ4["索引: GIN 索引
一行搞定全部路径"] end style MJ4 fill:#ffe6e6 style PJ4 fill:#e6ffe6
MySQL 的 JSON 本质是"存进去时是文本,读出来时再解析";PostgreSQL 的 JSONB 是"存进去时就解析为二进制格式,读的时候直接按偏移量定位"。差距体现在索引上尤为明显:
-- PostgreSQL JSONB:一行创建 GIN 索引,所有字段都可高效查询
CREATE INDEX idx_metadata ON orders USING GIN (metadata);
-- 查询 metadata 中的任意字段,都走索引
SELECT * FROM orders WHERE metadata @> '{"payment_method": "apple_pay"}';
SELECT * FROM orders WHERE metadata ? 'discount_code';
SELECT * FROM orders WHERE metadata->>'region' = 'UAE';
-- MySQL:每个要查询的 JSON 字段都需要单独处理
ALTER TABLE orders ADD COLUMN payment_method VARCHAR(50)
GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.payment_method')));
CREATE INDEX idx_payment_method ON orders(payment_method);
-- 每多查一个字段,就要重复一遍上面的步骤...在包含 100 万行、每行 JSON 约 2KB 的表上,性能差距一目了然:
| 操作 | MySQL JSON | PostgreSQL JSONB |
|---|---|---|
| 单字段精确查询(有索引) | ~15ms(需生成列) | ~3ms(GIN 索引) |
| 单字段精确查询(无索引) | ~800ms(全文档解析) | ~200ms(二进制直达) |
| 多条件组合查询 | 需多个生成列索引 | 一个 GIN 索引覆盖 |
| 存在性检查(某 key 是否存在) | JSON_CONTAINS_PATH,不走索引 | ? 操作符,走 GIN |
如果你的业务有"半结构化数据"需求(用户自定义属性、动态配置、多变的业务标签),JSONB 的优势是碾压级的。
索引能力
PostgreSQL 的索引能力碾压 MySQL:
| 索引特性 | PostgreSQL | MySQL |
|---|---|---|
| 部分索引 | ✅ WHERE status = 'active' | ❌ |
| 表达式索引 | ✅ ON (lower(email)) | ❌ 需要生成列绕行 |
| GIN 倒排 | ✅ 全文搜索、JSONB、数组 | ❌ |
| BRIN | ✅ 时序数据神器 | ❌ |
| 覆盖索引 | ✅ INCLUDE (col) | ✅ 但语法不同 |
实际收益:一个交易流水表,用 BRIN 索引按时间范围查询,索引大小是 B-tree 的 1/100,查询性能相当。
扩展性与生态
PostgreSQL 的扩展机制是它最强大的护城河:
- PostGIS:地理信息系统的事实标准
- TimescaleDB:时序数据库,完全兼容 SQL
- pgvector:向量数据库,AI 时代的新宠
- Citus:分布式扩展,微软出品
- FDW:可以把任何数据源(MySQL、MongoDB、Redis、S3...)当成表来查
MySQL 在这方面几乎是空白,你想要额外功能,就得引入额外系统。
深入 PostGIS:不仅仅是"存个坐标"
MySQL 的 GIS 和 PostGIS 的差距,不是"谁强一点"的问题,而是"业余选手 vs 职业运动员"的差距。
~30个函数"] end subgraph PostGIS["PostGIS"] PG1[1000+ 空间函数] PG2[拓扑 Topology] PG3[栅格 Raster] PG4[3D / 4D 对象] PG5[球面几何 Geography] PG6[路径规划 pgRouting] PG7[矢量切片 MVT] PG8[点云 Point Cloud] end MySQL_GIS -.->|"量级差距"| PostGIS style MySQL_GIS fill:#ffe6e6 style PostGIS fill:#e6ffe6
用一个外卖配送场景来感受差距——"找出距离骑手 3 公里内、且在配送区域多边形内的所有待配送订单,按距离排序":
PostgreSQL + PostGIS:
-- 一条 SQL 搞定,精确到球面距离
SELECT
o.order_id,
o.restaurant_name,
ST_Distance(
o.location::geography, -- 自动走球面计算
ST_SetSRID(ST_MakePoint(55.2708, 25.2048), 4326)::geography
) AS distance_meters
FROM orders o
JOIN delivery_zones dz ON ST_Contains(dz.polygon, o.location)
WHERE
o.status = 'pending'
AND ST_DWithin(
o.location::geography,
ST_SetSRID(ST_MakePoint(55.2708, 25.2048), 4326)::geography,
3000 -- 3公里,单位:米
)
ORDER BY distance_meters
LIMIT 20;
-- 索引:GiST 空间索引,查询毫秒级
CREATE INDEX idx_orders_location ON orders USING GIST (location);MySQL:
-- MySQL 的困境
SELECT
o.order_id,
o.restaurant_name,
ST_Distance_Sphere(
o.location,
ST_GeomFromText('POINT(55.2708 25.2048)')
) AS distance_meters
FROM orders o
WHERE
o.status = 'pending'
AND ST_Distance_Sphere(
o.location,
ST_GeomFromText('POINT(55.2708 25.2048)')
) < 3000
ORDER BY distance_meters
LIMIT 20;
-- 问题1:ST_Distance_Sphere 不走空间索引,全表扫描
-- 问题2:没有 ST_DWithin 的等价优化
-- 问题3:配送区域多边形判断精度有限
-- 问题4:需要 MBRContains + 二次过滤来绕行PostGIS 的杀手级特性远不止距离计算:
道路网络、行政边界
确保几何对象共享边界"] PostGIS --> Raster["🗺️ 栅格处理
卫星图像、高程模型
地形分析"] PostGIS --> ThreeD["📐 3D 支持
建筑信息模型 BIM
城市三维可视化"] PostGIS --> pgRouting["🛣️ pgRouting
最短路径 Dijkstra/A*
旅行商问题 TSP"] PostGIS --> MVT["🧩 矢量切片 MVT
直接生成地图瓦片
替代 GeoServer"] style PostGIS fill:#4CAF50,color:#fff
💡 如果你的项目涉及外卖配送、网约车、地图导航、物流规划,PostgreSQL + PostGIS 几乎是唯一的开源生产级选择。MySQL 的 GIS 能力只够做"附近的人"这种入门功能。
深入 pgvector:AI 时代的终极加分项
2024-2025 年,RAG(检索增强生成)成为 AI 应用的标准架构。核心需求是:把文本/图片转成向量,然后快速找到最相似的内容。
OpenAI / Cohere] Embed --> Vec["向量 [0.12, -0.34, ...]
768~3072 维"] Vec --> Store[存储到哪?] Store --> Option1["专用向量数据库
Milvus / Pinecone / Weaviate"] Store --> Option2["PostgreSQL + pgvector"] Option1 --> Problem["❌ 额外运维
❌ 数据同步
❌ 事务不一致
❌ 增加架构复杂度"] Option2 --> Benefit["✅ 同一数据库
✅ 与业务数据 JOIN
✅ 事务保证
✅ 运维零增量"] style Problem fill:#ffcccc style Benefit fill:#ccffcc
pgvector 最大的杀手锏不是向量搜索本身——专用向量库在纯搜索速度上可能更快——而是它和业务数据在同一个库里:
-- 安装扩展
CREATE EXTENSION vector;
-- 业务数据 + 向量,同一张表
CREATE TABLE knowledge_base (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
category VARCHAR(50),
embedding vector(1536), -- OpenAI ada-002 输出维度
created_at TIMESTAMPTZ DEFAULT now()
);
-- HNSW 索引:近似最近邻
CREATE INDEX idx_kb_embedding ON knowledge_base
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 语义搜索 + 业务过滤,同一个查询!这是专用向量库做不到的
SELECT
title, content,
1 - (embedding <=> $1::vector) AS similarity
FROM knowledge_base
WHERE category = 'payment_docs' -- 业务字段过滤
ORDER BY embedding <=> $1::vector
LIMIT 5;| 维度 | pgvector | Milvus / Pinecone |
|---|---|---|
| 部署复杂度 | 一条 CREATE EXTENSION | 独立集群,需要独立运维 |
| 与业务数据关联 | 原生 JOIN、WHERE 过滤 | 需要应用层二次查询 |
| 事务保证 | 完整 ACID | 无或有限 |
| 亿级向量性能 | 中等(适合千万级) | 强(专为此优化) |
| 过滤+搜索混合 | 原生支持,高效 | 需要 pre-filter/post-filter |
结论:除非你的向量规模在亿级以上且需要极致检索延迟,否则 pgvector 足以覆盖绝大多数 AI 应用场景。一个数据库解决所有问题,这才是真正的工程效率。
为什么 MySQL 掉队了?
1. Oracle 的"养子"困境
2010年 Oracle 收购 Sun 后获得 MySQL,但 Oracle 自己有商业数据库要卖。MySQL 的发展变得保守且缓慢,社区信任度下降。MariaDB、Percona 的分裂进一步分散了生态。
2. 技术债务累积
MySQL 早期为了"简单"做的很多妥协,在现代场景下成了负担:
3. 云原生时代的不适应
现代应用需要:
- 逻辑复制:CDC、事件驱动架构 → PostgreSQL 原生支持,MySQL 需要 Canal/Debezium
- JSON 文档:灵活 schema → PostgreSQL JSONB 完胜
- 时序数据:IoT、监控 → TimescaleDB 无缝集成
- 向量搜索:AI 应用 → pgvector 直接用
4. 开发者偏好转移
年轻开发者更倾向于选择 PostgreSQL。这不仅是技术原因,也是文化原因——PostgreSQL 社区更开放、更活跃、更有创新精神。
公平地说:MySQL 的优势
批评要客观。MySQL 并非一无是处:
| 优势 | 说明 |
|---|---|
| 简单读写性能 | 简单 CRUD 场景,MySQL 略快 |
| 运维成熟度 | DBA 更多,资料更丰富 |
| 云服务成本 | Aurora MySQL 实例选择更多 |
| 存量系统 | 大量成功案例(微信支付早期) |
| 线程模型 | 连接开销比 PG 的进程模型小 |
如果你的团队只熟悉 MySQL,且业务场景简单,继续用 MySQL 完全没问题。
深入连接模型:进程 vs 线程的真实影响
上面提到的"线程模型"优势值得展开聊——这是 MySQL 为数不多的、结构性优于 PostgreSQL 的地方。
~10MB RSS] Client2[客户端2] --> Fork2[Backend 进程2
~10MB RSS] Client3[客户端3] --> Fork3[Backend 进程3
~10MB RSS] ClientN["客户端 5000+"] --> ForkN["进程 5000+
💥 内存爆炸"] end subgraph MY_Model["MySQL: 线程模型"] MC1[客户端1] --> Thread1[线程1
~256KB] MC2[客户端2] --> Thread2[线程2
~256KB] MC3[客户端3] --> Thread3[线程3
~256KB] MCN["客户端 5000+"] --> ThreadN["线程 5000+
✅ 内存可控"] end style ForkN fill:#ffcccc style ThreadN fill:#ccffcc
PostgreSQL 为每个连接 fork 一个独立进程,隔离性好但内存开销大。在超高并发连接场景(5000+),裸用 PostgreSQL 会吃掉大量内存。
解决方案:用 PgBouncer 做连接池,这是 PostgreSQL 生态的标配组件:
客户端 (5000连接) → PgBouncer (连接池) → PostgreSQL (50-100个实际连接)实际上,即使 MySQL 也推荐使用连接池(ProxySQL),所以这个差距在生产环境中被大幅缩小了。但如果你的架构是 Serverless(大量 Lambda 函数直连数据库),MySQL 的线程模型确实更友好。
选型决策树
JSON/数组/地理/向量} Q1 -->|是| PG1[选 PostgreSQL] Q1 -->|否| Q2{高并发写入?
金融/支付场景} Q2 -->|是| Q3{团队熟悉度} Q2 -->|否| Q4{需要扩展能力?
时序/全文搜索/GIS} Q3 -->|都熟悉| PG2[选 PostgreSQL
事务模型更可靠] Q3 -->|只熟悉MySQL| Q5{能接受学习成本?} Q5 -->|能| PG3[选 PostgreSQL
长期收益大] Q5 -->|不能| MY1[选 MySQL
但要注意间隙锁问题] Q4 -->|是| PG4[选 PostgreSQL] Q4 -->|否| Q6{团队现状} Q6 -->|有MySQL经验| MY2[MySQL 可以] Q6 -->|都是新手| PG5[选 PostgreSQL
学一个更强的] style PG1 fill:#ccffcc style PG2 fill:#ccffcc style PG3 fill:#ccffcc style PG4 fill:#ccffcc style PG5 fill:#ccffcc style MY1 fill:#ffffcc style MY2 fill:#ffffcc
除了决策树覆盖的维度,还有两个容易被忽略的"隐藏变量":
- 连接模型:如果是 Serverless 架构(大量短连接),MySQL 原生更友好;用 PostgreSQL 需要配 PgBouncer
- JSON 使用强度:如果业务中有大量半结构化数据查询,PostgreSQL 的 JSONB + GIN 索引带来的性能优势是量级差距,不是调优能弥补的
版本推荐(2026年1月)
如果你决定选择 PostgreSQL:
| 风格 | 推荐版本 | 说明 |
|---|---|---|
| 稳妥派 | PostgreSQL 17.4+ | 已验证1.5年,功能与稳定性的最佳平衡 |
| 激进派 | PostgreSQL 18.x | 增量备份增强,但验证时间较短 |
如果你决定继续使用 MySQL:
| 风格 | 推荐版本 | 说明 |
|---|---|---|
| 推荐 | MySQL 8.4 LTS | 长期支持版本 |
| 备选 | MySQL 9.x | Innovation 版本,新特性多但非 LTS |
结语
MySQL 并没有变差,是 PostgreSQL 变得太强了。
这就像诺基亚和 iPhone 的故事——诺基亚的手机依然能打电话,但智能手机重新定义了"手机"应该是什么样子。
PostgreSQL 用二十年时间证明了"做正确的事"的价值。那些当年被嘲笑的"过度设计",如今都成了最珍贵的特性。而 MySQL 早期为了"快速占领市场"做的妥协,变成了难以偿还的技术债务。
但也别忘了一个工程原则:最好的数据库不是功能最强的,而是你的团队能驾驭的。 PostgreSQL 的 VACUUM 调优、连接池配置、查询计划理解门槛都比 MySQL 高。如果团队没有足够的经验,贸然迁移可能带来的问题比技术收益更大。
正确的路径是:
渐进式迁移] F -->|不是| H[先优化架构和代码] style B fill:#ccffcc style E fill:#ffffcc style H fill:#ffffcc
新项目选 PostgreSQL,老项目别轻易动。 这不是技术信仰,是工程理性。
本文写于 2026 年 1 月。数据库领域变化快,具体选型请结合最新情况判断。