如果你从 MySQL 迁移到 PostgreSQL,最先感受到的不仅是更严格的 SQL 标准,还有更丰富的索引类型和更智能的查询优化器。但这些优势要真正发挥出来,需要正确的调优方法。

EXPLAIN ANALYZE:不只是看,还要跑

MySQL 的 EXPLAIN 只给预估,而 PostgreSQL 的 EXPLAIN ANALYZE 会实际执行查询并给出精确耗时:

1
2
3
4
5
6
7
8
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT u.*, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2025-01-01'
GROUP BY u.id
ORDER BY order_count DESC
LIMIT 20;

关键指标:actual time(第一个数字是首行延迟,第二个是总耗时)、Buffers shared hit/read(缓存命中 vs 磁盘读取)、rows vs planned rows(差距大说明统计信息过时)。

索引策略:PostgreSQL 的独特武器

B-Tree(默认)

适用于等值查询和范围查询,90% 的场景用它就够了。

BRIN(Block Range INdex)

特别适合时序数据和日志表。BRIN 不索引每一行,而是索引每个数据块的统计摘要——体积只有 B-Tree 的千分之一:

1
2
CREATE INDEX idx_logs_time ON api_logs USING BRIN (created_at)
WITH (pages_per_range = 32);

GIN(Generalized Inverted Index)

全文搜索和 JSONB 查询的利器:

1
2
3
4
5
-- JSONB 索引
CREATE INDEX idx_metadata ON documents USING GIN (metadata jsonb_path_ops);

-- 全文搜索索引
CREATE INDEX idx_content_fts ON articles USING GIN (to_tsvector('english', content));

覆盖索引(INCLUDE)

PostgreSQL 11+ 支持索引包含额外列,避免回表:

1
2
CREATE INDEX idx_users_covering ON users (id) INCLUDE (name, email);
SELECT id, name, email FROM users WHERE id = 123;

VACUUM 与 autovacuum

PostgreSQL 的 MVCC 机制会导致死元组(dead tuples)堆积。不及时清理会导致表膨胀、查询变慢甚至事务 ID 回卷。

调优 autovacuum:

1
2
3
4
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_analyze_scale_factor = 0.005
);

监控死元组:

1
2
3
4
5
SELECT schemaname, relname, n_dead_tup, n_live_tup,
round(n_dead_tup * 100.0 / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY dead_ratio DESC;

连接池:PgBouncer 配置

PostgreSQL 每个连接都是一个独立进程(不像 MySQL 的线程模型),连接开销大。生产环境必须用连接池:

1
2
3
4
5
6
7
[pgbouncer]
pool_mode = transaction
max_client_conn = 500
default_pool_size = 25

[databases]
mydb = host=localhost port=5432 dbname=mydb

总结

PostgreSQL 的性能调优不是照搬 MySQL 经验。EXPLAIN ANALYZE 给你精确数据,BRIN/GIN 索引解决特殊场景,VACUUM 调优是长期运行的基础,连接池是不可或缺的架构层。选对了工具组合,PG 的性能上限远超大多数人的想象。