Database · #mysql#postgresql#comparison

PostgreSQL vs MySQL深度对比分析

2025.01.15 10 min 3.9k
// 目录 · contents

引言

PostgreSQL和MySQL是当今最流行的两款开源关系型数据库。MySQL以简单易用和高性能著称,在Web应用领域占据主导地位;PostgreSQL则以功能丰富、标准符合度高和可扩展性强闻名,在复杂查询和数据完整性方面有着独特优势。本文将从多个维度深入对比两者的差异,帮助读者根据实际场景做出合理选择。

架构差异

进程模型 vs 线程模型

graph TD
    subgraph "PostgreSQL: 进程模型"
        PM[Postmaster<br/>主进程] --> W1[Backend<br/>Worker 1]
        PM --> W2[Backend<br/>Worker 2]
        PM --> W3[Backend<br/>Worker N]
        PM --> BW[Background Writer]
        PM --> WW[WAL Writer]
        PM --> AP[Autovacuum]
        PM --> CK[Checkpointer]
        W1 --> SM[Shared Memory<br/>Shared Buffers / WAL Buffers]
        W2 --> SM
        W3 --> SM
    end

    subgraph "MySQL InnoDB: 线程模型"
        MS[mysqld<br/>单进程] --> T1[Connection Thread 1]
        MS --> T2[Connection Thread 2]
        MS --> T3[Connection Thread N]
        MS --> BP[Buffer Pool]
        MS --> RT[Redo Thread]
        MS --> PT[Purge Thread]
        MS --> IO[IO Threads]
    end
维度 PostgreSQL MySQL (InnoDB)
并发模型 多进程(一连接一进程) 多线程(线程池)
内存共享 Shared Memory 进程内共享
连接开销 较大(fork进程) 较小(创建线程)
连接池 通常需要PgBouncer 内置线程池(企业版)
崩溃隔离 一个连接崩溃不影响其他 一个线程崩溃可能影响整个进程

存储引擎

graph LR
    subgraph MySQL
        A[SQL Layer] --> B[InnoDB]
        A --> C[MyISAM]
        A --> D[Memory]
        A --> E[NDB Cluster]
        A --> F[...]
    end

    subgraph PostgreSQL
        G[SQL Layer] --> H[Heap Storage<br/>统一的存储管理]
        H --> I[Table AM API<br/>可扩展接口]
        I --> J[Heap AM<br/>默认]
        I --> K[zheap<br/>实验性]
        I --> L[Columnar<br/>Citus扩展]
    end

MySQL的可插拔存储引擎是其标志性特点,不同表可以使用不同的存储引擎。PostgreSQL传统上只有一种存储方式(heap),但从12版本开始引入了Table Access Method API,允许扩展存储方式。

MVCC 实现差异

这是两者最本质的架构差异之一。

PostgreSQL: 多版本存储在堆表中

graph TD
    subgraph "PostgreSQL MVCC"
        A["Heap Page"] --> B["Tuple v1 (xmin=100, xmax=200)<br/>name='Alice', DEAD"]
        A --> C["Tuple v2 (xmin=200, xmax=300)<br/>name='Bob', DEAD"]
        A --> D["Tuple v3 (xmin=300, xmax=0)<br/>name='Carol', LIVE"]
    end

    subgraph "需要VACUUM清理死元组"
        E[VACUUM] -.->|清理| B
        E -.->|清理| C
    end

PostgreSQL的MVCC特点: - 旧版本(dead tuples)存储在堆表中,与活跃数据混在一起 - 更新操作实际上是”删除旧行 + 插入新行” - 需要VACUUM进程定期清理dead tuples,回收空间 - 每行有xmin(创建事务ID)和xmax(删除事务ID)两个系统列

MySQL InnoDB: Undo Log 版本链

graph LR
    subgraph "InnoDB MVCC"
        A["Clustered Index<br/>当前最新版本<br/>name='Carol'<br/>DB_TRX_ID=300"] --> B["Undo Log<br/>name='Bob'<br/>DB_TRX_ID=200"]
        B --> C["Undo Log<br/>name='Alice'<br/>DB_TRX_ID=100"]
    end

    subgraph "Purge线程清理"
        D[Purge Thread] -.->|清理不再需要的| C
    end

InnoDB的MVCC特点: - 聚簇索引中只保留最新版本 - 旧版本通过回滚指针(DB_ROLL_PTR)链接到Undo Log - Purge线程负责清理不再需要的undo log - 不需要像PostgreSQL那样的VACUUM操作

MVCC 对比影响

维度 PostgreSQL MySQL InnoDB
UPDATE性能 较慢(写新行+维护索引) 较快(原地更新+undo log)
表膨胀 需要VACUUM防止膨胀 无膨胀问题
读取性能 可能因死元组影响 稳定(通过版本链回溯)
索引维护 HOT优化减少索引更新 二级索引不变(指向主键)
运维复杂度 需要调优autovacuum 相对简单

索引类型对比

graph TD
    subgraph PostgreSQL索引
        P1[B-Tree] --> P1a[默认索引类型<br/>等值和范围查询]
        P2[Hash] --> P2a[等值查询]
        P3[GiST] --> P3a[地理数据 全文搜索<br/>范围类型 几何类型]
        P4[SP-GiST] --> P4a[不平衡树结构<br/>IP地址 电话号码]
        P5[GIN] --> P5a[倒排索引<br/>全文搜索 JSONB数组]
        P6[BRIN] --> P6a[块范围索引<br/>时序数据 极小的索引体积]
        P7[Bloom] --> P7a[布隆过滤器索引<br/>多列等值查询]
    end

    subgraph MySQL索引
        M1[B+Tree] --> M1a[默认索引类型]
        M2[Hash] --> M2a[Memory引擎<br/>自适应Hash索引]
        M3[Full-Text] --> M3a[全文索引<br/>InnoDB 5.6+]
        M4[R-Tree] --> M4a[空间索引<br/>MyISAM/InnoDB]
    end

PostgreSQL 特色索引示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 部分索引(Partial Index): 只索引满足条件的行
CREATE INDEX idx_active_users ON users (email)
WHERE status = 'active';
-- 只索引活跃用户,索引更小,查询更快

-- 表达式索引(Expression Index)
CREATE INDEX idx_lower_email ON users (LOWER(email));
-- 支持 WHERE LOWER(email) = '[email protected]'

-- GIN索引用于JSONB
CREATE INDEX idx_profile_tags ON users USING gin (profile_data jsonb_path_ops);
-- 支持 WHERE profile_data @> '{"tags": ["developer"]}'

-- BRIN索引用于时序数据(极小的索引体积)
CREATE INDEX idx_logs_created ON access_logs USING brin (created_at);
-- 100GB的表,BRIN索引可能只有几百KB

-- GiST索引用于地理查询
CREATE INDEX idx_locations ON places USING gist (coordinates);
-- 支持 WHERE coordinates <-> point(116.4, 39.9) < 0.01

-- 覆盖索引(INCLUDE语法,PostgreSQL 11+)
CREATE INDEX idx_user_email ON users (email) INCLUDE (name, phone);
-- email用于查找,name和phone只存储不参与索引排序

MySQL 索引特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 联合索引(最左前缀)
ALTER TABLE orders ADD INDEX idx_user_status(user_id, status, created_at);

-- 前缀索引
ALTER TABLE articles ADD INDEX idx_title(title(20));

-- 全文索引(InnoDB 5.6+)
ALTER TABLE articles ADD FULLTEXT INDEX ft_content(title, content)
WITH PARSER ngram; -- 支持中文分词
SELECT * FROM articles WHERE MATCH(title, content) AGAINST('数据库' IN BOOLEAN MODE);

-- 不可见索引(MySQL 8.0+,用于测试删除索引的影响)
ALTER TABLE orders ALTER INDEX idx_status INVISIBLE;
-- 优化器不再使用此索引,但索引仍在维护
-- 确认无影响后再真正删除
ALTER TABLE orders DROP INDEX idx_status;

-- 降序索引(MySQL 8.0+)
ALTER TABLE orders ADD INDEX idx_user_time(user_id ASC, created_at DESC);

JSON 支持对比

PostgreSQL JSONB

PostgreSQL的JSONB是真正的二进制JSON存储,支持丰富的运算符和索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 创建含JSONB列的表
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
attributes JSONB
);

-- 插入数据
INSERT INTO products (name, attributes) VALUES
('iPhone 15', '{"brand": "Apple", "specs": {"ram": 8, "storage": 256}, "tags": ["smartphone", "5G"]}'),
('Galaxy S24', '{"brand": "Samsung", "specs": {"ram": 12, "storage": 512}, "tags": ["smartphone", "AI"]}');

-- 丰富的查询运算符
-- 包含查询
SELECT * FROM products WHERE attributes @> '{"brand": "Apple"}';

-- 路径查询
SELECT * FROM products WHERE attributes -> 'specs' ->> 'ram' = '8';
SELECT * FROM products WHERE (attributes #>> '{specs,storage}')::int > 128;

-- 数组元素查询
SELECT * FROM products WHERE attributes -> 'tags' ? 'AI';

-- JSONPath查询(PostgreSQL 12+)
SELECT * FROM products WHERE attributes @? '$.specs ? (@.ram > 8)';

-- GIN索引加速JSON查询
CREATE INDEX idx_attrs ON products USING gin (attributes);
CREATE INDEX idx_attrs_path ON products USING gin (attributes jsonb_path_ops);

-- JSONB聚合
SELECT
attributes ->> 'brand' AS brand,
AVG((attributes #>> '{specs,ram}')::int) AS avg_ram
FROM products
GROUP BY attributes ->> 'brand';

-- JSONB修改
UPDATE products SET attributes = jsonb_set(
attributes,
'{specs,ram}',
'16'
) WHERE name = 'Galaxy S24';

MySQL JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- 创建含JSON列的表
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
attributes JSON
);

-- JSON函数查询
SELECT * FROM products
WHERE JSON_EXTRACT(attributes, '$.brand') = 'Apple';
-- 或使用简写
SELECT * FROM products
WHERE attributes -> '$.brand' = '"Apple"';

-- 虚拟生成列 + 索引(MySQL解决JSON索引的方式)
ALTER TABLE products
ADD COLUMN brand VARCHAR(50) GENERATED ALWAYS AS (attributes ->> '$.brand') VIRTUAL,
ADD INDEX idx_brand (brand);

-- JSON_TABLE: 将JSON数组展开为关系表(MySQL 8.0+)
SELECT p.name, t.tag
FROM products p,
JSON_TABLE(p.attributes, '$.tags[*]' COLUMNS (
tag VARCHAR(50) PATH '$'
)) t;

-- JSON修改
UPDATE products SET attributes = JSON_SET(
attributes,
'$.specs.ram', 16
) WHERE name = 'Galaxy S24';

JSON 功能对比

特性 PostgreSQL JSONB MySQL JSON
存储格式 二进制,解析后存储 二进制(MySQL 5.7.8+)
索引支持 GIN原生索引 需要生成列+B-Tree
运算符 丰富(@>, ?, #>, 等) 较少(->,->>)
JSONPath 完整支持(12+) 部分支持
修改性能 整体重写 部分更新(8.0+优化)
验证 JSONB自动验证格式 JSON自动验证格式

全文搜索

PostgreSQL 内置全文搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 创建包含全文搜索向量的列
ALTER TABLE articles ADD COLUMN tsv tsvector;
UPDATE articles SET tsv = to_tsvector('english', title || ' ' || content);
CREATE INDEX idx_tsv ON articles USING gin(tsv);

-- 全文搜索查询
SELECT * FROM articles
WHERE tsv @@ to_tsquery('english', 'database & optimization');

-- 搜索结果排序
SELECT title, ts_rank(tsv, query) AS rank
FROM articles, to_tsquery('english', 'database | index') query
WHERE tsv @@ query
ORDER BY rank DESC;

-- 搜索结果高亮
SELECT title,
ts_headline('english', content, to_tsquery('database'), 'StartSel=<b>, StopSel=</b>')
FROM articles
WHERE tsv @@ to_tsquery('database');

-- 中文全文搜索(需要安装 zhparser 扩展)
CREATE EXTENSION zhparser;
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;
SELECT * FROM articles WHERE to_tsvector('chinese', content) @@ to_tsquery('chinese', '数据库');

MySQL 全文搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_content(title, content) WITH PARSER ngram;

-- 自然语言模式搜索
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE);

-- 布尔模式搜索
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('+database -mysql' IN BOOLEAN MODE);

-- 查询扩展搜索
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('database' WITH QUERY EXPANSION);

对于生产环境的全文搜索需求,通常建议使用专门的搜索引擎(如Elasticsearch)。

复制与高可用

graph TD
    subgraph "PostgreSQL 复制"
        PA[Primary] -->|WAL Streaming| PB[Standby 1<br/>同步复制]
        PA -->|WAL Streaming| PC[Standby 2<br/>异步复制]
        PA -->|Logical Replication| PD[Subscriber<br/>选择性复制]
    end

    subgraph "MySQL 复制"
        MA[Source] -->|Binlog<br/>异步/半同步/组复制| MB[Replica 1]
        MA -->|Binlog| MC[Replica 2]
        subgraph "Group Replication"
            MD[Node 1] <-->|Paxos| ME[Node 2]
            ME <-->|Paxos| MF[Node 3]
            MD <-->|Paxos| MF
        end
    end
特性 PostgreSQL MySQL
物理复制 WAL Streaming(默认) Binlog(Statement/Row/Mixed)
逻辑复制 Logical Replication(10+) Binlog + GTID
同步复制 支持(synchronous_commit) 半同步复制
多主复制 BDR扩展 Group Replication / NDB Cluster
延迟复制 recovery_min_apply_delay CHANGE MASTER TO MASTER_DELAY
级联复制 支持 支持

性能对比

基准测试场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-- 高并发简单查询(OLTP)
-- MySQL通常略优: 线程模型开销更小,连接管理更高效

-- 复杂分析查询(OLAP)
-- PostgreSQL通常优势明显:
-- 1. 更强的查询优化器(支持Hash Join、Merge Join等多种策略)
-- 2. 并行查询支持更完善
-- 3. CTE(WITH语句)支持更好
-- 4. 窗口函数更丰富

-- 窗口函数示例(PostgreSQL更丰富)
SELECT
user_id,
amount,
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total,
PERCENT_RANK() OVER (PARTITION BY user_id ORDER BY amount) AS pct_rank,
LAG(amount, 1) OVER (PARTITION BY user_id ORDER BY created_at) AS prev_amount
FROM orders;

-- PostgreSQL独有: LATERAL JOIN
SELECT u.*, recent_orders.*
FROM users u,
LATERAL (
SELECT * FROM orders o
WHERE o.user_id = u.id
ORDER BY o.created_at DESC
LIMIT 5
) recent_orders;

-- PostgreSQL独有: FILTER子句
SELECT
COUNT(*) AS total,
COUNT(*) FILTER (WHERE status = 'paid') AS paid_count,
SUM(amount) FILTER (WHERE status = 'shipped') AS shipped_amount
FROM orders;

典型性能数据

1
2
3
4
5
6
7
8
9
10
场景                         | PostgreSQL | MySQL (InnoDB)
-----------------------------|-----------|---------------
简单主键查询 (QPS) | ~80,000 | ~100,000
10表JOIN复杂查询 (QPS) | ~2,000 | ~800
批量INSERT (行/秒) | ~150,000 | ~200,000
JSONB查询 (QPS) | ~50,000 | ~30,000
全文搜索 (QPS) | ~10,000 | ~8,000
并行扫描大表 (GB/秒) | ~2.5 | ~1.5

注: 以上数据仅为参考数量级,实际性能取决于硬件、配置和数据特征

PostgreSQL 独有特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
-- 1. 丰富的数据类型
-- 数组
CREATE TABLE events (
id SERIAL PRIMARY KEY,
tags TEXT[]
);
INSERT INTO events (tags) VALUES (ARRAY['tech', 'database', 'pg']);
SELECT * FROM events WHERE 'database' = ANY(tags);

-- 范围类型
CREATE TABLE reservations (
room_id INT,
during TSTZRANGE, -- 时间范围
EXCLUDE USING gist (room_id WITH =, during WITH &&) -- 排他约束防止重叠
);
INSERT INTO reservations VALUES (1, '[2025-01-01, 2025-01-05)');
-- 插入重叠时段会报错

-- 自定义类型
CREATE TYPE address AS (
street TEXT,
city TEXT,
zipcode TEXT
);

-- 2. 可扩展性
-- 自定义函数(多种语言)
CREATE FUNCTION fibonacci(n INT) RETURNS INT AS $$
BEGIN
IF n <= 1 THEN RETURN n; END IF;
RETURN fibonacci(n-1) + fibonacci(n-2);
END;
$$ LANGUAGE plpgsql;

-- PostGIS地理信息扩展
CREATE EXTENSION postgis;
SELECT ST_Distance(
ST_Point(116.4, 39.9)::geography,
ST_Point(121.5, 31.2)::geography
) / 1000 AS distance_km;

-- 3. 高级约束
-- CHECK约束
ALTER TABLE products ADD CONSTRAINT price_positive CHECK (price > 0);

-- 排他约束
-- (上面的范围类型示例)

-- 4. 物化视图
CREATE MATERIALIZED VIEW monthly_stats AS
SELECT date_trunc('month', created_at) AS month,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY 1;

-- 刷新物化视图(支持并发刷新)
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_stats;

使用场景推荐

flowchart TD
    A[选择数据库] --> B{应用类型?}

    B -->|Web应用<br/>高并发简单查询| C[MySQL]
    B -->|数据分析<br/>复杂查询| D[PostgreSQL]
    B -->|地理信息GIS| E[PostgreSQL + PostGIS]
    B -->|JSON文档存储| F[PostgreSQL JSONB]
    B -->|时序数据| G[PostgreSQL + TimescaleDB]

    C --> C1[优势: 成熟生态<br/>简单运维<br/>丰富的工具链]
    D --> D1[优势: 强大优化器<br/>丰富数据类型<br/>标准兼容性]

    H{团队情况?}
    H -->|MySQL经验丰富| C
    H -->|需要高级SQL特性| D
    H -->|云原生首选| I["两者皆可<br/>AWS RDS / Cloud SQL"]
选择MySQL的场景 选择PostgreSQL的场景
Web后端,简单CRUD为主 复杂业务逻辑,大量JOIN和分析
读多写少,需要读写分离 需要JSONB存储半结构化数据
已有MySQL技术栈和运维经验 地理信息系统(GIS)
需要大量第三方工具支持 需要自定义数据类型和函数
互联网公司主流选择 数据完整性要求极高的场景

总结

PostgreSQL和MySQL各有所长,选择取决于具体场景:

  • 架构层面:PostgreSQL的多进程模型提供更好的稳定性,MySQL的线程模型有更高的连接效率
  • MVCC实现:PostgreSQL的堆内多版本需要VACUUM维护,MySQL的Undo Log方案更简洁
  • 索引能力:PostgreSQL的索引类型远比MySQL丰富,GIN、GiST、BRIN等为特殊场景提供了强力支持
  • JSON支持:PostgreSQL的JSONB功能显著强于MySQL的JSON
  • 查询能力:PostgreSQL的优化器更强,对复杂查询(多表JOIN、窗口函数、CTE)的支持更好
  • 生态和运维:MySQL的生态更广泛,运维工具链更成熟,社区更大

在实际项目中,不必拘泥于一种数据库——很多公司同时使用MySQL和PostgreSQL,各取所长,服务于不同的业务场景。

作者 · authorzt
发布 · date2025-01-15
篇幅 · length3.9k 字 · 10 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论