正在加载,请稍候…

PostgreSQL 性能调优:索引、查询计划与配置

优化 PostgreSQL 以用于生产环境——EXPLAIN ANALYZE、索引策略(B-tree、GIN、BRIN)、部分索引、PgBouncer 连接池以

PostgreSQL 性能调优:索引、查询计划与配置

PostgreSQL 性能基础

慢查询是应用程序性能问题的首要原因。以下是如何系统性地诊断和修复它们。

PostgreSQL 性能调优:索引、查询计划与配置 插图

EXPLAIN ANALYZE

-- 始终使用 EXPLAIN (ANALYZE, BUFFERS) 获取真实执行数据
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created_at > '2026-01-01'
GROUP BY u.id, u.name
ORDER BY order_count DESC
LIMIT 20;

-- 需要关注的关键指标:
-- Seq Scan vs Index Scan
-- Actual rows vs estimated rows(差异大 = 统计信息过时)
-- Buffers: shared hit vs read(高 read = 磁盘 I/O)
-- Loops: 嵌套循环次数

索引类型

-- B-tree(默认):等值、范围、ORDER BY
CREATE INDEX idx_users_email ON users (email);
CREATE INDEX idx_orders_created ON orders (created_at DESC);

-- 复合索引:从左到右前缀规则
CREATE INDEX idx_orders_user_status ON orders (user_id, status, created_at DESC);
-- 适用于:WHERE user_id = X AND status = 'pending'
-- 也适用于:WHERE user_id = X ORDER BY created_at DESC

-- 部分索引:索引行的子集
CREATE INDEX idx_orders_pending ON orders (user_id, created_at)
WHERE status = 'pending';
-- 体积更小,仅查询 pending 时更快

-- GIN 索引:全文搜索、JSONB、数组
CREATE INDEX idx_posts_search ON posts USING gin(to_tsvector('english', title || ' ' || content));
CREATE INDEX idx_products_tags ON products USING gin(tags);  -- 数组列

-- BRIN:时间序列数据,物理相关性高
CREATE INDEX idx_events_timestamp ON events USING brin(timestamp);
-- 体积非常小,适用于仅追加的时间序列

PostgreSQL 性能调优:索引、查询计划与配置 插图

查询优化模式

-- 使用 CTE 提高可读性,但需谨慎(PG < 12 中 CTE 是优化屏障)
WITH user_stats AS MATERIALIZED (
  SELECT user_id, COUNT(*) as total, SUM(amount) as revenue
  FROM orders
  WHERE status = 'completed'
  GROUP BY user_id
)
SELECT u.name, s.total, s.revenue
FROM users u
JOIN user_stats s ON s.user_id = u.id
ORDER BY s.revenue DESC
LIMIT 10;

-- 避免 N+1:使用窗口函数
SELECT
  id, name,
  COUNT(*) OVER (PARTITION BY department) AS dept_size,
  AVG(salary) OVER (PARTITION BY department) AS avg_dept_salary,
  salary - AVG(salary) OVER (PARTITION BY department) AS salary_diff
FROM employees;

-- 高效分页(基于游标)
-- 不好:OFFSET 10000 会扫描 10000 行
SELECT * FROM posts ORDER BY created_at DESC OFFSET 10000 LIMIT 20;

-- 好:游标分页
SELECT * FROM posts
WHERE created_at < '2026-05-01T12:00:00'
ORDER BY created_at DESC
LIMIT 20;

关键配置参数

# postgresql.conf 调优

# 内存
shared_buffers = 25% of RAM           # 例如 16GB 服务器设为 4GB
effective_cache_size = 75% of RAM     # 用于查询规划器估算
work_mem = 64MB                       # 每个排序/哈希操作
maintenance_work_mem = 512MB          # 用于 VACUUM、CREATE INDEX

# 检查点
checkpoint_completion_target = 0.9
wal_buffers = 64MB

# 并行
max_parallel_workers_per_gather = 4   # 并行查询工作进程
max_parallel_workers = 8

# 日志
log_min_duration_statement = 1000     # 记录超过 1 秒的查询
log_checkpoints = on
log_lock_waits = on

PostgreSQL 性能调优:索引、查询计划与配置 插图

使用 PgBouncer 进行连接池管理

# pgbouncer.ini
[databases]
myapp = host=127.0.0.1 port=5432 dbname=myapp

[pgbouncer]
pool_mode = transaction          # 最适合大多数 Web 应用
max_client_conn = 1000
default_pool_size = 25           # 实际 PG 连接数
reserve_pool_size = 5
server_idle_timeout = 600

自动清理调优

-- 检查表膨胀
SELECT
  tablename,
  n_dead_tup,
  n_live_tup,
  round(n_dead_tup::numeric / NULLIF(n_live_tup, 0) * 100, 2) AS bloat_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;

-- 针对高写入表的每表自动清理设置
ALTER TABLE orders SET (
  autovacuum_vacuum_scale_factor = 0.01,    -- 死元组占比 1% 时清理
  autovacuum_analyze_scale_factor = 0.01,
  autovacuum_vacuum_cost_delay = 2
);

监控查询

-- 查找慢查询(需要 pg_stat_statements 扩展)
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

-- 查找缺失的索引
SELECT relname, seq_scan, idx_scan,
  seq_scan - idx_scan AS diff
FROM pg_stat_user_tables
WHERE seq_scan > 100
ORDER BY diff DESC;

-- 查找索引使用情况
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE idx_scan = 0  -- 未使用的索引
AND NOT indisprimary
ORDER BY pg_relation_size(indexrelid) DESC;