PostgreSQL vs MySQL 深度对比:2026年数据库选型指南
从架构设计、SQL标准支持、并发控制、索引类型、扩展能力、性能基准、运维复杂度等维度全面对比PostgreSQL 17和MySQL 8.4。包含真实场景的选型建议和迁移注意事项。
选型困境:PostgreSQL 还是 MySQL?
“新项目用 PostgreSQL 还是 MySQL?” 这是技术选型中最常被问到的问题之一。
我们团队同时维护着基于 MySQL 和 PostgreSQL 的系统,在不同场景下各有优劣。这篇文章基于 2026 年的最新版本(PostgreSQL 17 vs MySQL 8.4),从多个维度做一次诚实的对比。
先说结论:没有银弹,只有合适的场景。但如果你让我给一个默认选择——2026 年新项目默认选 PostgreSQL,除非有明确的理由选 MySQL。
架构设计对比
存储引擎
MySQL:插件式存储引擎
-- MySQL 可以按表选择存储引擎
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
...
) ENGINE=InnoDB; -- 事务支持
CREATE TABLE audit_logs (
id BIGINT PRIMARY KEY,
...
) ENGINE=MyISAM; -- 无事务,但读性能好(基本已废弃)
MySQL 的多引擎架构是历史优势也是负担。InnoDB 已经是绝对主流,MyISAM 基本退出历史舞台。
PostgreSQL:统一存储引擎
PostgreSQL 只有一个存储引擎,所有表使用相同的底层机制。这意味着:
- 不需要纠结引擎选择
- 所有功能(事务、MVCC、全文搜索)对所有表可用
- 行为一致,没有”这个引擎不支持 X”的问题
我们的看法:统一引擎是 PostgreSQL 的优势。MySQL 的多引擎在今天已经没什么实际价值了。
进程模型
MySQL:线程模型
每个连接一个线程,共享内存空间。线程切换开销小,但一个线程崩溃可能影响其他连接。
PostgreSQL:进程模型
每个连接一个独立进程(fork)。进程隔离性好,一个连接崩溃不影响其他连接。缺点是连接创建开销大,这也是为什么 PostgreSQL 强烈推荐使用连接池(PgBouncer)。
MVCC 实现
这是两者最大的架构差异:
| 特性 | MySQL (InnoDB) | PostgreSQL |
|---|---|---|
| 实现方式 | Undo Log(回滚段) | 多版本元组直接存储 |
| 旧版本存储 | 在 Undo 表空间中 | 在数据页中 |
| 清理机制 | Purge 线程异步清理 | VACUUM(膨胀问题) |
| UPDATE 开销 | 写 Undo + 数据页 | 插入新版本 + 标记旧版本 |
| 索引影响 | 二级索引需要回表 | 所有索引都存储版本信息 |
PostgreSQL 的”膨胀”问题:UPDATE 频繁的表会不断增长,需要 VACUUM 回收空间。这是 PostgreSQL 运维中最需要注意的问题。
-- PostgreSQL 查看膨胀情况
SELECT
schemaname || '.' || relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
n_dead_tup AS dead_tuples,
round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
SQL 标准支持
PostgreSQL 在 SQL 标准支持方面明显领先:
-- PostgreSQL 支持的特性(MySQL 不支持或不完善)
-- 1. CTE 的 UPDATE/DELETE
WITH deleted AS (
DELETE FROM old_orders WHERE created_at < '2025-01-01' RETURNING *
)
INSERT INTO order_archive SELECT * FROM deleted;
-- 2. DISTINCT ON(保留每组第一条)
SELECT DISTINCT ON (category_id)
category_id, name, price
FROM products
ORDER BY category_id, price DESC;
-- 3. 窗口函数更丰富
SELECT
date,
revenue,
array_agg(revenue) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS last_7_days
FROM daily_revenue;
-- 4. 全文搜索(内置,不需要外部引擎)
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ to_tsquery('english', 'database & performance');
-- 5. 数组和 JSON 深度支持
SELECT * FROM products
WHERE tags @> ARRAY['electronics', 'sale']; -- 数组包含
-- 6. CHECK 约束强制执行
ALTER TABLE users ADD CONSTRAINT age_check CHECK (age >= 0 AND age < 150);
-- MySQL 8.0.16 之前不强制执行 CHECK!
索引能力
| 索引类型 | PostgreSQL | MySQL |
|---|---|---|
| B-Tree | ✅ | ✅(默认) |
| Hash | ✅ | ✅(8.0+ 支持,仅等值查询) |
| GIN(倒排索引) | ✅ | ❌ |
| GiST(空间索引) | ✅ | ❌(用 R-Tree) |
| BRIN(块范围索引) | ✅ | ❌ |
| 部分索引 | ✅ | ❌(8.0 不支持) |
| 表达式索引 | ✅ | ✅(8.0.13+ 函数索引) |
| 覆盖索引 | ✅(INCLUDE) | ✅(二级索引自动覆盖主键) |
PostgreSQL 的杀手锏——部分索引:
-- 只为活跃订单建索引,节省 90% 空间
CREATE INDEX idx_active_orders ON orders (user_id, created_at)
WHERE status = 'ACTIVE';
-- 只为未删除的记录建唯一约束
CREATE UNIQUE INDEX idx_active_email ON users (email)
WHERE deleted_at IS NULL;
GIN 索引的实际应用:
-- 标签搜索(替代 Elasticsearch 的轻量场景)
CREATE INDEX idx_article_tags ON articles USING GIN (tags);
-- 查询瞬间完成
SELECT * FROM articles WHERE tags @> ARRAY['postgresql', 'database'];
-- JSONB 查询
CREATE INDEX idx_user_preferences ON user_profiles USING GIN (preferences jsonb_path_ops);
SELECT * FROM user_profiles WHERE preferences @> '{"theme": "dark"}';
复制和高可用
| 特性 | PostgreSQL | MySQL |
|---|---|---|
| 物理复制 | ✅(WAL 流复制) | ✅(Binlog 复制) |
| 逻辑复制 | ✅(PG 10+) | ✅(Binlog 本身就是逻辑的) |
| 同步复制 | ✅ | ✅(半同步) |
| 级联复制 | ✅ | ✅ |
| 自动故障转移 | 需要 Patroni/Pgpool | 需要 Orchestrator/MGR |
PostgreSQL 流复制配置:
# postgresql.conf(主库)
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1024 # 保留 1GB WAL
# standby.signal 文件(备库,PG 12+)
# 在数据目录创建此文件即可
# 然后在 postgresql.conf 中配置:
primary_conninfo = 'host=primary_host port=5432 user=replicator'
MySQL 组复制(MGR):
-- MySQL 8.0 的 Group Replication 提供了内置的高可用
-- 单主模式
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;
-- 查看集群状态
SELECT * FROM performance_schema.replication_group_members;
扩展生态
PostgreSQL 扩展
-- 时序数据
CREATE EXTENSION timescaledb;
-- 地理空间
CREATE EXTENSION postgis;
-- 向量搜索(AI 应用)
CREATE EXTENSION pgvector;
-- 列式存储(OLAP)
CREATE EXTENSION citus;
-- 全文搜索(中文分词)
CREATE EXTENSION zhparser;
PostgreSQL 的扩展机制允许在不修改核心代码的情况下增加功能。pgvector 尤其值得关注——在 AI 时代,用 PostgreSQL 做向量数据库比引入新的向量数据库更简单。
MySQL 插件
MySQL 也有插件机制,但生态远不如 PostgreSQL 丰富:
- Clone 插件(8.0.17+):快速克隆实例
- Keyring 插件:加密密钥管理
- 半同步复制插件
性能基准
注意:性能测试高度依赖场景,以下数据仅供参考
简单读写(Sysbench oltp_read_write)
| 并发数 | PostgreSQL 17 | MySQL 8.4 |
|---|---|---|
| 16 | 12,000 TPS | 14,000 TPS |
| 64 | 35,000 TPS | 38,000 TPS |
| 128 | 42,000 TPS | 45,000 TPS |
| 256 | 40,000 TPS | 43,000 TPS |
MySQL 在简单 OLTP 场景下略有优势(得益于线程模型和 InnoDB 的优化)。
复杂查询(TPC-H 类查询)
| 查询类型 | PostgreSQL | MySQL |
|---|---|---|
| 多表 JOIN(5 表) | 快 2-3x | 基准 |
| 子查询 | 快 1.5-2x | 基准 |
| 窗口函数 | 快 3-5x | 基准 |
| 聚合 + GROUP BY | 相近 | 相近 |
PostgreSQL 在复杂查询上有明显优势,得益于更先进的查询优化器(支持更多 JOIN 算法)和并行查询能力。
插入性能
| 场景 | PostgreSQL | MySQL |
|---|---|---|
| 单行插入 | 慢 20-30% | 基准 |
| 批量插入(1000 行) | 相近 | 相近 |
| COPY/Bulk Load | 快 2x | 基准 |
PostgreSQL 的 COPY 命令在批量导入上远超 MySQL 的 LOAD DATA。
运维复杂度
日常运维对比
| 任务 | PostgreSQL | MySQL |
|---|---|---|
| 安装 | apt install postgresql-17 | apt install mysql-server-8.4 |
| 备份 | pg_dump / pg_basebackup | mysqldump / xtrabackup |
| 时间点恢复 | PITR(WAL 归档) | Binlog 恢复 |
| 升级大版本 | pg_upgrade(需停机) | In-place upgrade |
| 监控 | pg_stat_* 视图丰富 | performance_schema |
| 连接池 | 必需(PgBouncer) | 可选(ProxySQL) |
PostgreSQL 的运维痛点
1. VACUUM 管理
-- 查看 VACUUM 进度
SELECT * FROM pg_stat_progress_vacuum;
-- 配置自动 VACUUM
ALTER SYSTEM SET autovacuum_vacuum_scale_factor = 0.05; -- 表 5% 变化时触发
ALTER SYSTEM SET autovacuum_analyze_scale_factor = 0.02;
-- 对特定表手动 VACUUM
VACUUM ANALYZE large_table;
VACUUM FULL large_table; -- 回收空间给 OS(锁表!)
2. 事务 ID 回卷
-- 监控事务年龄
SELECT
datname,
age(datfrozenxid) AS xid_age,
pg_size_pretty(pg_database_size(datname)) AS db_size
FROM pg_database
WHERE age(datfrozenxid) > 100000000
ORDER BY age(datfrozenxid) DESC;
当事务 ID 接近 20 亿上限时,PostgreSQL 会强制进入只读模式。需要定期执行 VACUUM FREEZE。
MySQL 的运维痛点
1. 在线 DDL 限制
-- 有些 DDL 不支持 ALGORITHM=INPLACE
-- 比如:修改列类型(大部分情况)、删除主键等
ALTER TABLE orders MODIFY COLUMN amount DECIMAL(12,2), ALGORITHM=INPLACE;
-- 如果报错,说明需要 COPY(锁表),需要借助 pt-online-schema-change
2. 主从延迟
MySQL 的主从延迟问题比 PostgreSQL 更常见(因为 Binlog 是逻辑复制,而 PostgreSQL 的流复制是物理复制)。
场景选型建议
| 场景 | 推荐 | 理由 |
|---|---|---|
| 新项目(无历史包袱) | PostgreSQL | 功能更完善,SQL 标准支持好 |
| 复杂查询/分析 | PostgreSQL | 查询优化器更强 |
| 地理空间数据 | PostgreSQL + PostGIS | 生态最好 |
| AI 向量搜索 | PostgreSQL + pgvector | 减少组件 |
| 高并发简单 OLTP | MySQL | 线程模型有一定优势 |
| 与现有 MySQL 生态集成 | MySQL | 避免引入新技术栈 |
| WordPress/常见 CMS | MySQL | 生态兼容 |
| 金融/强一致性要求 | PostgreSQL | 更严格的标准遵循 |
| 时序数据 | PostgreSQL + TimescaleDB | 一个数据库搞定 |
| 简单的 KV 存储 | 两者皆可 | 甚至可以考虑 Redis |
从 MySQL 迁移到 PostgreSQL 的注意事项
如果决定迁移,以下是我们总结的关键点:
语法差异
-- 反引号 vs 双引号
-- MySQL
SELECT `user_id`, `created_at` FROM `users`;
-- PostgreSQL
SELECT "user_id", "created_at" FROM "users";
-- 或者(推荐):不用引号
SELECT user_id, created_at FROM users;
-- LIMIT OFFSET
-- MySQL
SELECT * FROM users LIMIT 10 OFFSET 20;
-- PostgreSQL(两种语法都支持)
SELECT * FROM users LIMIT 10 OFFSET 20;
SELECT * FROM users OFFSET 20 LIMIT 10;
-- UPDATE JOIN
-- MySQL
UPDATE orders o JOIN users u ON o.user_id = u.id
SET o.user_name = u.name;
-- PostgreSQL
UPDATE orders o
SET user_name = u.name
FROM users u
WHERE o.user_id = u.id;
数据类型映射
| MySQL | PostgreSQL |
|---|---|
| TINYINT | SMALLINT |
| DATETIME | TIMESTAMP |
| TEXT | TEXT |
| BLOB | BYTEA |
| ENUM | 自定义 TYPE 或 CHECK 约束 |
| JSON | JSONB(推荐)/ JSON |
| AUTO_INCREMENT | SERIAL / BIGSERIAL / GENERATED AS IDENTITY |
迁移工具
# pgloader:最成熟的 MySQL → PostgreSQL 迁移工具
pgloader mysql://user:pass@host/dbname postgresql://user:pass@host/dbname
# 配置文件方式
# migration.load
LOAD DATABASE
FROM mysql://user:pass@localhost/source_db
INTO postgresql://user:pass@localhost/target_db
WITH
create_tables,
create_indexes,
reset_sequences,
disable_triggers
SET
maintenance_work_mem to '128MB',
work_mem to '12MB'
CAST
type datetime to timestamptz drop default drop not null using zero-dates-to-null;
总结
PostgreSQL 和 MySQL 都是成熟的开源数据库,选择哪个取决于:
- 默认选 PostgreSQL:除非有明确理由选 MySQL
- MySQL 的优势:简单 OLTP、现有生态、运维人员熟悉度
- PostgreSQL 的优势:复杂查询、扩展能力、SQL 标准支持、数据完整性
- 不要纠结”哪个更好”:两个都足够好,选你团队最熟悉的
- 性能差异在大多数场景可以忽略:应用层优化比数据库选型重要得多