PostgreSQL 性能调优完全指南

数据库

为什么 2026 年选择 PostgreSQL?

PostgreSQL 在 2026 年已经稳居全球最受欢迎的开源关系型数据库之首。凭借其强大的扩展性、丰富的数据类型和卓越的 SQL 标准兼容性,它已成为从初创公司到大型企业的首选数据库。

PostgreSQL 核心优势

维度 PostgreSQL MySQL SQL Server Oracle
扩展性 极强(扩展框架) 中等 中等
数据类型 极丰富(JSONB、GIS、数组) 中等 丰富 丰富
SQL 标准兼容 最接近标准 中等
开源 完全开源(PostgreSQL License) GPL 部分开源 闭源
并发控制 MVCC(优秀) MVCC MVCC MVCC
分区能力 原生声明式分区 原生分区 原生分区 原生分区

2026 年 PostgreSQL 的关键进展

  1. PostgreSQL 18:并行查询进一步增强,逻辑复制性能大幅提升
  2. 增量排序(Incremental Sort)优化:多列排序场景性能提升 3-5 倍
  3. JSON 路径表达式增强:更强大的 JSONB 查询与索引支持
  4. 异步 I/O 支持:底层 I/O 架构改进,大幅提升高并发场景吞吐
  5. 逻辑复制改进:支持 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:如何快速定位慢查询?

  1. 启用 pg_stat_statements 扩展
  2. 查询 pg_stat_statementstotal_exec_time 排序
  3. 对慢查询使用 EXPLAIN ANALYZE 分析执行计划
  4. 检查是否有 Seq Scan、高 cost 节点
  5. 针对性添加索引或改写查询

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 年依然是功能最强大的开源关系型数据库,其性能调优需要系统性地从查询分析索引设计参数配置架构优化四个维度入手。关键要点回顾:

  1. EXPLAIN ANALYZE 是性能调优的起点,理解每种扫描和连接类型的适用场景
  2. 索引选型 至关重要:B-tree 通用、GIN 处理 JSONB/数组、BRIN 适合时序数据
  3. 参数调优 四大核心:shared_buffers、work_mem、effective_cache_size、WAL 配置
  4. 分区策略 是大表治理的基石:范围分区最常用、哈希分区保均匀
  5. VACUUM 调优 防止表膨胀:autovacuum 必须开启并合理配置
  6. 监控体系 是保障:pg_stat_statements + postgres_exporter + Grafana 三件套

本站提供浏览器本地工具,免注册即可试用 →

#PostgreSQL#性能调优#数据库#SQL优化#教程