PostgreSQL 性能调优完全指南
为什么 2026 年选择 PostgreSQL?
PostgreSQL 在 2026 年已经稳居全球最受欢迎的开源关系型数据库之首。凭借其强大的扩展性、丰富的数据类型和卓越的 SQL 标准兼容性,它已成为从初创公司到大型企业的首选数据库。
PostgreSQL 核心优势
| 维度 | PostgreSQL | MySQL | SQL Server | Oracle |
|---|---|---|---|---|
| 扩展性 | 极强(扩展框架) | 中等 | 中等 | 强 |
| 数据类型 | 极丰富(JSONB、GIS、数组) | 中等 | 丰富 | 丰富 |
| SQL 标准兼容 | 最接近标准 | 中等 | 高 | 高 |
| 开源 | 完全开源(PostgreSQL License) | GPL | 部分开源 | 闭源 |
| 并发控制 | MVCC(优秀) | MVCC | MVCC | MVCC |
| 分区能力 | 原生声明式分区 | 原生分区 | 原生分区 | 原生分区 |
2026 年 PostgreSQL 的关键进展
- PostgreSQL 18:并行查询进一步增强,逻辑复制性能大幅提升
- 增量排序(Incremental Sort)优化:多列排序场景性能提升 3-5 倍
- JSON 路径表达式增强:更强大的 JSONB 查询与索引支持
- 异步 I/O 支持:底层 I/O 架构改进,大幅提升高并发场景吞吐
- 逻辑复制改进:支持 DDL 复制,大幅简化数据同步架构
💡 使用 SQL 格式化 工具美化 PostgreSQL 查询语句,便于分析执行计划。
EXPLAIN ANALYZE 深度解析
EXPLAIN ANALYZE 是 PostgreSQL 性能调优最核心的工具,它不仅展示查询计划,还执行查询并返回实际耗时统计。
基础用法
-- 查看执行计划(不执行)
EXPLAIN SELECT o.order_id, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-01-01'
AND o.status = 'shipped';
-- 执行查询并返回实际统计
EXPLAIN ANALYZE SELECT o.order_id, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-01-01'
AND o.status = 'shipped';
-- 查看详细输出(含缓冲区统计)
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE status = 'shipped';
扫描类型详解
顺序扫描(Seq Scan)
EXPLAIN ANALYZE SELECT * FROM orders WHERE amount > 1000;
-- Seq Scan on orders (cost=0.00..15432.00 rows=100000 width=88)
-- Filter: (amount > 1000)
- 适用场景:表数据量小、查询返回大部分行、无合适索引
- 优化方向:若返回行数 < 总行数 5%,考虑添加索引
索引扫描(Index Scan)
EXPLAIN ANALYZE SELECT * FROM orders WHERE order_id = 12345;
-- Index Scan using orders_pkey on orders (cost=0.42..8.44 rows=1 width=88)
-- Index Cond: (order_id = 12345)
- 适用场景:等值查询、高选择性条件、返回少量行
- 优化方向:确保索引列顺序与查询条件匹配
位图扫描(Bitmap Scan)
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'shipped';
-- Bitmap Heap Scan on orders (cost=523.12..8234.56 rows=15000 width=88)
-- Recheck Cond: (status = 'shipped')
-- -> Bitmap Index Scan on idx_orders_status (cost=0.00..519.37 rows=15000)
- 适用场景:返回中等数量行(表的 5%-15%)、多条件组合查询
- 优化方向:Bitmap Index Scan + Bitmap Heap Scan 组合,适合中等选择性
连接类型详解
嵌套循环连接(Nested Loop)
EXPLAIN ANALYZE
SELECT * FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.customer_id = 100;
-- Nested Loop (cost=0.85..234.56 rows=50 width=200)
-- -> Index Scan using idx_orders_customer on orders o
-- -> Index Scan using idx_items_order on order_items oi
- 适用场景:小表驱动大表、内表有索引、返回少量行
- 优化方向:确保内表连接列有索引
哈希连接(Hash Join)
EXPLAIN ANALYZE
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
-- Hash Join (cost=345.67..12345.89 rows=100000 width=200)
-- Hash Cond: (o.customer_id = c.customer_id)
-- -> Seq Scan on orders o
-- -> Hash
-- -> Seq Scan on customers c
- 适用场景:大表连接大表、等值连接、无排序需求
- 优化方向:确保 Build 侧(Hash 表)能放入 work_mem
归并连接(Merge Join)
EXPLAIN ANALYZE
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
ORDER BY o.customer_id;
-- Merge Join (cost=0.86..15678.90 rows=100000 width=200)
-- Merge Cond: (o.customer_id = c.customer_id)
-- -> Index Scan using idx_orders_customer on orders o
-- -> Index Scan using customers_pkey on customers c
- 适用场景:两侧数据已排序、等值连接、大数据量
- 优化方向:确保两侧连接列有索引(已排序)
EXPLAIN 输出关键字段
| 字段 | 含义 | 关注点 |
|---|---|---|
| cost | 估算代价(启动..总代价) | 总代价过高需优化 |
| rows | 估算返回行数 | 与实际行数偏差大需 ANALYZE |
| width | 平均行宽度(字节) | 过宽考虑减少 SELECT 列 |
| actual time | 实际耗时(毫秒) | 关注慢节点 |
| actual rows | 实际返回行数 | 与估算偏差大需更新统计 |
| loops | 执行次数 | 嵌套循环中 loops × time = 总耗时 |
| Buffers | 缓冲区命中统计 | shared hit 高表示缓存友好 |
💡 使用 JSON 格式化 工具解析
FORMAT JSON输出的 EXPLAIN 结果。
索引类型与选型实战
PostgreSQL 提供了丰富的索引类型,选择合适的索引类型是性能调优的关键。
B-tree 索引(默认)
-- 创建 B-tree 索引
CREATE INDEX idx_orders_date ON orders (order_date);
-- 复合 B-tree 索引
CREATE INDEX idx_orders_status_date ON orders (status, order_date);
-- 适用场景:等值查询、范围查询、排序、LIKE 'prefix%'
-- 最常用索引类型,覆盖 90%+ 场景
GIN 索引(倒排索引)
-- JSONB 字段索引
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
-- 数组字段索引
CREATE INDEX idx_articles_tags ON articles USING GIN (tags);
-- 全文搜索索引
CREATE INDEX idx_docs_content ON documents USING GIN (to_tsvector('english', content));
-- 查询示例
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
SELECT * FROM articles WHERE tags @> ARRAY['postgresql', 'performance'];
SELECT * FROM documents WHERE to_tsvector('english', content) @@ to_tsquery('postgresql & tuning');
- 适用场景:JSONB、数组、全文搜索
- 注意:GIN 索引构建慢,查询快;考虑
fastupdate = on加速写入
GiST 索引(通用搜索树)
-- 地理空间索引(PostGIS)
CREATE INDEX idx_stores_location ON stores USING GiST (location);
-- 范围类型索引
CREATE INDEX idx_events_period ON events USING GiST (time_range);
-- 查询示例
SELECT * FROM stores WHERE ST_DWithin(location, ST_Point(116.4, 39.9), 5000);
SELECT * FROM events WHERE time_range && '[2026-01-01, 2026-06-30)'::daterange;
- 适用场景:地理空间、范围类型、树结构
- 注意:GiST 支持自定义操作符类
BRIN 索引(块范围索引)
-- 时序数据索引
CREATE INDEX idx_logs_created ON logs USING BRIN (created_at);
-- 自定义块大小
CREATE INDEX idx_logs_created ON logs USING BRIN (created_at) WITH (pages_per_range = 32);
-- 查询示例
SELECT * FROM logs WHERE created_at >= '2026-06-01' ORDER BY created_at DESC LIMIT 100;
- 适用场景:大表中自然排序的列(时序数据、自增 ID)
- 优势:索引体积极小(通常为 B-tree 的 1/100)
- 注意:仅适合数据物理有序的列,选择性查询不如 B-tree
Hash 索引
-- 创建 Hash 索引
CREATE INDEX idx_users_email_hash ON users USING HASH (email);
-- 适用场景:纯等值查询
-- 注意:PostgreSQL 10+ 后 Hash 索引已支持 WAL,可安全使用
-- 但通常 B-tree 已足够,Hash 索引使用场景有限
索引类型选型决策表
| 场景 | 推荐索引 | 原因 |
|---|---|---|
| 等值/范围查询 | B-tree | 最通用,性能稳定 |
| JSONB 包含查询 | GIN | 原生支持 @> 操作符 |
| 全文搜索 | GIN | 配合 tsvector 高效检索 |
| 地理空间查询 | GiST | PostGIS 标准搭配 |
| 范围重叠查询 | GiST | 原生支持 && 操作符 |
| 时序数据范围查询 | BRIN | 索引极小,适合物理有序数据 |
| 纯等值查询(超大表) | Hash | 等值查询略快于 B-tree |
部分索引与表达式索引
部分索引(Partial Index)
-- 仅索引未完成的订单(减少索引体积)
CREATE INDEX idx_orders_pending ON orders (customer_id, order_date)
WHERE status = 'pending';
-- 仅索引活跃用户
CREATE INDEX idx_users_active ON users (last_login_at)
WHERE is_active = true;
-- 查询自动使用部分索引
SELECT * FROM orders WHERE status = 'pending' AND customer_id = 100;
- 优势:索引体积大幅减小,写入开销降低
- 注意:查询条件必须满足 WHERE 子句才能使用索引
表达式索引
-- 不区分大小写查询
CREATE INDEX idx_users_email_lower ON users (LOWER(email));
-- 日期截取查询
CREATE INDEX idx_orders_date_trunc ON orders (DATE(order_date));
-- JSONB 特定键索引
CREATE INDEX idx_products_color ON products ((attributes->>'color'));
-- 查询示例
SELECT * FROM users WHERE LOWER(email) = 'admin@example.com';
SELECT * FROM orders WHERE DATE(order_date) = '2026-06-11';
SELECT * FROM products WHERE attributes->>'color' = 'red';
- 优势:避免查询时计算表达式,直接走索引
- 注意:查询条件必须与索引表达式完全一致
复合索引列顺序原则
-- 遵循:等值条件列 → 范围条件列 → 排序列
CREATE INDEX idx_orders_optimal ON orders (status, customer_id, order_date DESC);
-- 覆盖索引:包含所有查询列,避免回表
CREATE INDEX idx_orders_covering ON orders (status, order_date)
INCLUDE (customer_id, amount);
查询优化模式
避免 SELECT *
-- 反模式
SELECT * FROM orders WHERE customer_id = 100;
-- 优化:只查需要的列
SELECT order_id, order_date, amount
FROM orders WHERE customer_id = 100;
分页优化
-- 反模式:OFFSET 在大偏移量时性能极差
SELECT * FROM orders ORDER BY order_id OFFSET 100000 LIMIT 20;
-- 优化 1:Keyset 分页(游标分页)
SELECT * FROM orders WHERE order_id > 100000 ORDER BY order_id LIMIT 20;
-- 优化 2:覆盖索引 + JOIN
SELECT o.* FROM orders o
JOIN (SELECT order_id FROM orders ORDER BY order_id OFFSET 100000 LIMIT 20) t
ON o.order_id = t.order_id;
子查询优化
-- 反模式:相关子查询
SELECT * FROM orders o
WHERE EXISTS (SELECT 1 FROM order_items oi WHERE oi.order_id = o.order_id AND oi.price > 100);
-- 优化:改写为 JOIN
SELECT DISTINCT o.* FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE oi.price > 100;
CTE 优化
-- PostgreSQL 12+:CTE 默认可内联优化
WITH recent_orders AS (
SELECT * FROM orders WHERE order_date >= '2026-01-01'
)
SELECT ro.*, c.customer_name
FROM recent_orders ro
JOIN customers c ON ro.customer_id = c.customer_id;
-- 需要物化 CTE 时显式指定
WITH MATERIALIZED expensive_calc AS (
SELECT customer_id, SUM(amount) AS total
FROM orders GROUP BY customer_id
)
SELECT * FROM expensive_calc WHERE total > 10000;
批量操作优化
-- 批量插入(单条 INSERT 多值)
INSERT INTO orders (customer_id, amount, status)
VALUES
(1, 100.00, 'pending'),
(2, 200.00, 'shipped'),
(3, 150.00, 'pending');
-- 批量更新使用 CASE
UPDATE orders SET status = CASE
WHEN order_id = 1 THEN 'shipped'
WHEN order_id = 2 THEN 'delivered'
WHEN order_id = 3 THEN 'cancelled'
ELSE status
END
WHERE order_id IN (1, 2, 3);
-- 使用 COPY 快速导入
COPY orders FROM '/data/orders.csv' WITH (FORMAT csv, HEADER true);
连接池配置(PgBouncer)
PgBouncer 是 PostgreSQL 最流行的连接池,能有效解决连接数过多导致的性能问题。
为什么需要连接池?
每个 PostgreSQL 连接会 fork 一个进程,消耗约 10MB 内存。1000 个连接 = 10GB 内存开销,且进程切换成本高。
PgBouncer 安装与配置
# 安装 PgBouncer
sudo apt install pgbouncer
# 启动 PgBouncer
sudo systemctl start pgbouncer
sudo systemctl enable pgbouncer
; /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 10000
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 10
reserve_pool_timeout = 3
server_idle_timeout = 300
server_lifetime = 3600
log_connections = 0
log_disconnections = 0
stats_period = 60
连接池模式
| 模式 | 特点 | 适用场景 |
|---|---|---|
| session | 会话级复用,连接绑定到客户端 | 需要会话变量、临时表 |
| transaction | 事务级复用,事务结束后连接归还 | 大多数 Web 应用(推荐) |
| statement | 语句级复用,每条语句后归还 | 短查询、无事务场景 |
连接数规划
-- 查看当前连接数
SELECT count(*) FROM pg_stat_activity;
-- 按状态分组
SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY count DESC;
-- 查看 PostgreSQL 最大连接数
SHOW max_connections;
-- 推荐公式
-- PgBouncer default_pool_size = CPU 核心数 × 2 + 磁盘数
-- PostgreSQL max_connections = 所有 PgBouncer pool_size 之和 + 预留
核心参数调优
shared_buffers
-- 共享缓冲区大小,PostgreSQL 最核心参数
-- 推荐值:系统总内存的 25%,但不超过 8GB(Linux)
ALTER SYSTEM SET shared_buffers = '4GB';
work_mem
-- 排序和哈希操作的内存上限
-- 推荐值:总内存 / (max_connections × 3),通常 16MB-256MB
ALTER SYSTEM SET work_mem = '64MB';
-- 查看当前 work_mem 导致的磁盘排序
SELECT query, calls, temp_blks_written
FROM pg_stat_statements
WHERE temp_blks_written > 0
ORDER BY temp_blks_written DESC LIMIT 10;
effective_cache_size
-- 查询优化器对系统缓存的估算值(不实际分配内存)
-- 推荐值:系统总内存的 50%-75%
ALTER SYSTEM SET effective_cache_size = '12GB';
WAL 相关参数
-- WAL 写入方式:open_datasync(Linux 默认)、fdatasync、fsync 等
ALTER SYSTEM SET wal_sync_method = 'open_datasync';
-- WAL 缓冲区大小
ALTER SYSTEM SET wal_buffers = '64MB';
-- 检查点间隔
ALTER SYSTEM SET checkpoint_timeout = '10min';
ALTER SYSTEM SET max_wal_size = '2GB';
ALTER SYSTEM SET min_wal_size = '512MB';
-- 完整性保证(生产环境务必开启)
ALTER SYSTEM SET fsync = 'on';
ALTER SYSTEM SET synchronous_commit = 'on';
关键参数速查表
| 参数 | 推荐值(16GB 内存) | 说明 |
|---|---|---|
| shared_buffers | 4GB | 共享缓冲区 |
| work_mem | 64MB | 排序/哈希内存 |
| effective_cache_size | 12GB | 优化器缓存估算 |
| maintenance_work_mem | 1GB | 维护操作内存 |
| wal_buffers | 64MB | WAL 缓冲区 |
| max_wal_size | 2GB | WAL 最大大小 |
| checkpoint_timeout | 10min | 检查点间隔 |
| random_page_cost | 1.1(SSD) | 随机页代价 |
| effective_io_concurrency | 200(SSD) | 并发 I/O 数 |
| max_connections | 100-200 | 最大连接数 |
# 使用 pgtune 自动生成推荐配置
pgtune -i postgresql.conf -M 16GB -T web -c 200
分区策略
PostgreSQL 10+ 支持原生声明式分区,是处理大表的核心手段。
范围分区(Range)
-- 按月分区订单表
CREATE TABLE orders (
order_id BIGINT,
customer_id INT,
order_date DATE,
amount DECIMAL(10,2),
status TEXT
) PARTITION BY RANGE (order_date);
CREATE TABLE orders_2026_q1 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE orders_2026_q2 PARTITION OF orders
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
CREATE TABLE orders_2026_q3 PARTITION OF orders
FOR VALUES FROM ('2026-07-01') TO ('2026-10-01');
CREATE TABLE orders_2026_q4 PARTITION OF orders
FOR VALUES FROM ('2026-10-01') TO ('2027-01-01');
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
列表分区(List)
-- 按地区分区
CREATE TABLE customers (
customer_id BIGINT,
name TEXT,
region TEXT,
created_at TIMESTAMP
) PARTITION BY LIST (region);
CREATE TABLE customers_north PARTITION OF customers
FOR VALUES IN ('北京', '天津', '河北');
CREATE TABLE customers_south PARTITION OF customers
FOR VALUES IN ('广东', '福建', '海南');
CREATE TABLE customers_default PARTITION OF customers DEFAULT;
哈希分区(Hash)
-- 哈希分区(均匀分布)
CREATE TABLE access_logs (
log_id BIGINT,
user_id INT,
path TEXT,
created_at TIMESTAMP
) PARTITION BY HASH (user_id);
CREATE TABLE access_logs_p0 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE access_logs_p1 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE access_logs_p2 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE access_logs_p3 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
分区维护
-- 自动创建未来分区(使用 pg_partman 扩展)
CREATE EXTENSION pg_partman;
SELECT partman.create_parent(
p_parent_table := 'public.orders',
p_control := 'order_date',
p_type := 'range',
p_interval := '1 month',
p_premake := 6
);
-- 分区裁剪验证
EXPLAIN SELECT * FROM orders WHERE order_date = '2026-06-11';
-- 应只扫描 orders_2026_q2 分区
-- 删除旧分区(归档后删除)
DROP TABLE orders_2024_q1;
并行查询
PostgreSQL 9.6+ 引入并行查询,18 版本已大幅增强。
启用并行查询
-- 开启并行查询
ALTER SYSTEM SET max_parallel_workers_per_gather = 4;
ALTER SYSTEM SET max_parallel_workers = 8;
ALTER SYSTEM SET parallel_tuple_cost = 0.01;
ALTER SYSTEM SET parallel_setup_cost = 100;
-- 最小表大小触发并行(默认 8MB)
ALTER SYSTEM SET min_parallel_table_scan_size = '8MB';
-- 并行工作者数随表大小增长
ALTER SYSTEM SET min_parallel_index_scan_size = '512kB';
并行查询验证
EXPLAIN ANALYZE SELECT COUNT(*), SUM(amount)
FROM orders WHERE order_date >= '2026-01-01';
-- Gather Merge
-- -> Sort
-- -> Partial Aggregate
-- -> Parallel Seq Scan on orders
并行查询适用场景
| 场景 | 并行效果 | 说明 |
|---|---|---|
| 大表全表扫描 | 极好 | 并行 Seq Scan |
| 大表聚合 | 极好 | 并行 Aggregation |
| 大表 JOIN | 好 | 并行 Hash Join |
| 索引扫描 | 一般 | 并行 Bitmap Scan |
| 小表查询 | 无效果 | 低于 min_parallel_table_scan_size |
| CTE(物化) | 不支持 | MATERIALIZED CTE 不并行 |
VACUUM 与 autovacuum 调优
PostgreSQL 的 MVCC 机制会产生死元组(dead tuples),必须通过 VACUUM 回收空间。
手动 VACUUM
-- 普通 VACUUM(不回收磁盘空间,只标记可复用)
VACUUM orders;
-- VACUUM FULL(回收磁盘空间,锁表!)
VACUUM FULL orders;
-- 并行 VACUUM(PostgreSQL 12+,不锁表)
VACUUM (PARALLEL 4) orders;
-- 分析统计信息
ANALYZE orders;
-- 同时 VACUUM + ANALYZE
VACUUM ANALYZE orders;
autovacuum 调优
-- 开启 autovacuum(默认开启)
ALTER SYSTEM SET autovacuum = 'on';
-- autovacuum 工作者数量
ALTER SYSTEM SET autovacuum_max_workers = 4;
-- 触发阈值(当死元组数超过此值时触发)
-- 公式:autovacuum_vacuum_scale_factor × 表行数 + autovacuum_vacuum_threshold
ALTER SYSTEM SET autovacuum_vacuum_scale_factor = 0.1;
ALTER SYSTEM SET autovacuum_vacuum_threshold = 50;
-- 分析触发阈值
ALTER SYSTEM SET autovacuum_analyze_scale_factor = 0.05;
ALTER SYSTEM SET autovacuum_analyze_threshold = 50;
-- autovacuum 单次处理成本限制
ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 2000;
ALTER SYSTEM SET autovacuum_vacuum_cost_delay = '2ms';
单表 autovacuum 调优
-- 对频繁更新的表降低触发阈值
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_analyze_scale_factor = 0.01,
autovacuum_vacuum_cost_limit = 5000
);
-- 查看表的 VACUUM 统计
SELECT relname, n_live_tup, n_dead_tup,
last_vacuum, last_autovacuum,
last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC LIMIT 10;
监控 VACUUM 进度
-- PostgreSQL 12+ 查看 VACUUM 进度
SELECT pid, datname, relid::regclass, phase,
heap_blks_total, heap_blks_scanned, heap_blks_vacuumed,
index_vacuum_count, num_dead_item_ids
FROM pg_stat_progress_vacuum;
pg_stat_statements 慢查询分析
pg_stat_statements 是 PostgreSQL 最强大的慢查询分析工具。
安装与启用
-- 加载扩展
CREATE EXTENSION pg_stat_statements;
-- 修改 postgresql.conf
-- shared_preload_libraries = 'pg_stat_statements'
-- pg_stat_statements.track = all
-- pg_stat_statements.max = 10000
Top N 慢查询
-- 按总耗时排序
SELECT query, calls, total_exec_time, mean_exec_time,
min_exec_time, max_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC LIMIT 10;
-- 按平均耗时排序
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
WHERE calls > 10
ORDER BY mean_exec_time DESC LIMIT 10;
-- 按磁盘 I/O 排序
SELECT query, calls, shared_blks_read, shared_blks_hit,
ROUND(shared_blks_hit::numeric / NULLIF(shared_blks_hit + shared_blks_read, 0), 4) AS cache_hit_ratio
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC LIMIT 10;
-- 重置统计
SELECT pg_stat_statements_reset();
缓存命中率分析
-- 整体缓存命中率(应 > 99%)
SELECT
sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;
-- 单表缓存命中率
SELECT relname,
heap_blks_hit, heap_blks_read,
ROUND(heap_blks_hit::numeric / NULLIF(heap_blks_hit + heap_blks_read, 0), 4) AS cache_hit_ratio
FROM pg_statio_user_tables
WHERE heap_blks_read > 0
ORDER BY heap_blks_read DESC;
常见性能反模式
反模式 1:过度索引
-- 问题:每个索引都会增加写入开销
-- 检查未使用的索引
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
-- 删除未使用索引
DROP INDEX CONCURRENTLY idx_orders_unused;
反模式 2:大事务
-- 问题:长事务阻止 VACUUM 回收死元组,导致表膨胀
-- 检查长事务
SELECT pid, now() - xact_start AS duration, query, state
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'active')
AND xact_start IS NOT NULL
ORDER BY duration DESC;
-- 设置事务超时(PostgreSQL 17+)
ALTER SYSTEM SET idle_in_transaction_session_timeout = '60000';
反模式 3:类型不匹配
-- 问题:隐式类型转换导致无法使用索引
-- 反模式
SELECT * FROM orders WHERE order_id = '12345'; -- VARCHAR 与 BIGINT 不匹配
-- 优化:确保类型一致
SELECT * FROM orders WHERE order_id = 12345;
反模式 4:OR 条件滥用
-- 反模式
SELECT * FROM orders WHERE customer_id = 100 OR amount > 5000;
-- 优化:使用 UNION ALL
SELECT * FROM orders WHERE customer_id = 100
UNION ALL
SELECT * FROM orders WHERE amount > 5000 AND customer_id != 100;
反模式 5:函数导致索引失效
-- 反模式:对索引列使用函数
SELECT * FROM orders WHERE DATE(created_at) = '2026-06-11';
-- 优化:使用范围查询
SELECT * FROM orders WHERE created_at >= '2026-06-11' AND created_at < '2026-06-12';
-- 或使用表达式索引
CREATE INDEX idx_orders_created_date ON orders (DATE(created_at));
Prometheus + Grafana 监控
postgres_exporter 部署
# 安装 postgres_exporter
wget https://github.com/prometheus-community/postgres_exporter/releases/download/v0.15.0/postgres_exporter-0.15.0.linux-amd64.tar.gz
tar xzf postgres_exporter-0.15.0.linux-amd64.tar.gz
# 配置数据源
export DATA_SOURCE_NAME="postgresql://monitor:password@localhost:5432/mydb?sslmode=disable"
# 启动
./postgres_exporter --web.listen-address=:9187
Prometheus 配置
scrape_configs:
- job_name: 'postgresql'
static_configs:
- targets: ['localhost:9187']
metric_relabel_configs:
- source_labels: [__name__]
regex: 'pg_.*'
action: keep
关键监控指标
| 指标 | 含义 | 告警阈值 |
|---|---|---|
| pg_stat_activity_count | 当前连接数 | > max_connections × 80% |
| pg_stat_database_deadlocks | 死锁数 | > 0 |
| pg_stat_user_tables_n_dead_tup | 死元组数 | > 总行数 × 10% |
| pg_stat_statements_mean_exec_time | 平均查询耗时 | P99 > 500ms |
| pg_replication_lag | 复制延迟 | > 30s |
| pg_database_size_bytes | 数据库大小 | > 磁盘 85% |
告警规则示例
groups:
- name: postgresql_alerts
rules:
- alert: PostgreSQLHighConnections
expr: pg_stat_activity_count > 160
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQL 连接数过高"
description: "当前连接数超过 160,持续 5 分钟"
- alert: PostgreSQLReplicationLag
expr: pg_replication_lag > 30
for: 2m
labels:
severity: critical
annotations:
summary: "PostgreSQL 复制延迟过高"
description: "复制延迟超过 30 秒"
- alert: PostgreSQLDeadTuplesHigh
expr: pg_stat_user_tables_n_dead_tup / pg_stat_user_tables_n_live_tup > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "PostgreSQL 死元组比例过高"
description: "死元组占比超过 10%,需要 VACUUM"
💡 使用 哈希加密 工具生成监控告警 webhook 的签名密钥。
常见问题 FAQ
Q1:shared_buffers 设置多大合适?
推荐设置为系统总内存的 25%,但不超过 8GB(Linux)。因为 PostgreSQL 依赖操作系统的页面缓存,过大的 shared_buffers 会与 OS 缓存争抢内存。例如 16GB 内存的服务器,设置 shared_buffers = 4GB。
Q2:什么时候应该使用 BRIN 索引而非 B-tree?
当表数据量极大(> 1 亿行)且列值与物理存储顺序相关时(如时间戳、自增 ID),BRIN 索引体积仅为 B-tree 的 1/100,但查询性能接近。典型场景:日志表的时间列、时序数据的采集时间。
Q3:如何判断是否需要分区?
当单表数据量超过 1000 万行,且查询通常只涉及部分数据(如按时间范围查询),就应该考虑分区。分区裁剪可以大幅减少扫描数据量。
Q4:VACUUM FULL 什么时候使用?
VACUUM FULL 会锁表并重建整张表,回收所有碎片空间。仅在表膨胀严重(死元组 > 50%)且无法通过普通 VACUUM 回收时使用。推荐使用 pg_repack 扩展作为在线替代方案。
Q5:PgBouncer 的 transaction 模式有什么限制?
transaction 模式下,事务结束后连接会被回收,因此:不能使用临时表(跨事务)、不能使用 SET 设置会话级参数(跨事务)、不能使用 LISTEN/NOTIFY(跨事务)、不能使用预备语句(跨事务)。如需这些功能,使用 session 模式。
Q6:如何快速定位慢查询?
- 启用
pg_stat_statements扩展 - 查询
pg_stat_statements按total_exec_time排序 - 对慢查询使用
EXPLAIN ANALYZE分析执行计划 - 检查是否有 Seq Scan、高 cost 节点
- 针对性添加索引或改写查询
Q7:random_page_cost 应该设置多少?
- SSD/NVMe:设置为 1.1(接近顺序 I/O 代价)
- HDD:保持默认 4.0
- 这个参数直接影响优化器选择索引扫描还是顺序扫描的决策
Q8:PostgreSQL 的连接数上限怎么定?
每个连接约消耗 10MB 内存。推荐公式:max_connections = 可用内存(GB) × 100,但实际连接数应通过 PgBouncer 池化控制。PostgreSQL 的 max_connections 设置为 100-200 即可,外部连接由 PgBouncer 管理。
总结
PostgreSQL 在 2026 年依然是功能最强大的开源关系型数据库,其性能调优需要系统性地从查询分析、索引设计、参数配置、架构优化四个维度入手。关键要点回顾:
- EXPLAIN ANALYZE 是性能调优的起点,理解每种扫描和连接类型的适用场景
- 索引选型 至关重要:B-tree 通用、GIN 处理 JSONB/数组、BRIN 适合时序数据
- 参数调优 四大核心:shared_buffers、work_mem、effective_cache_size、WAL 配置
- 分区策略 是大表治理的基石:范围分区最常用、哈希分区保均匀
- VACUUM 调优 防止表膨胀:autovacuum 必须开启并合理配置
- 监控体系 是保障:pg_stat_statements + postgres_exporter + Grafana 三件套
本站提供浏览器本地工具,免注册即可试用 →