7 个 PostgreSQL 性能调优实战技巧:索引策略、查询优化、主从复制全解析
从索引设计到查询优化,从配置调优到主从复制,手把手提升 PostgreSQL 性能。
为什么 PostgreSQL 性能会变差?
随着数据量增长,常见的性能瓶颈:
技巧1:索引策略——不是建得越多越好
1.1 查看缺失索引
-- 找出执行最慢的查询
SELECT
query,
calls,
total_time,
mean_time,
rows
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;
-- 查看表上的索引使用情况
SELECT
schemaname,
relname AS table_name,
indexrelname AS index_name,
idx_scan AS index_scans,
seq_scan AS sequential_scans
FROM pg_stat_user_tables t
JOIN pg_stat_user_indexes i ON t.relid = i.relid
WHERE seq_scan > idx_scan * 10 -- 顺序扫描远多于索引扫描
ORDER BY seq_scan DESC;
1.2 创建合适的索引
-- ❌ 错误:对低区分度字段建索引(如性别) CREATE INDEX idx_gender ON users(gender); -- 只有2个值,索引效果不佳 -- ✅ 正确:对高区分度字段建索引 CREATE INDEX idx_email ON users(email); -- email 唯一,区分度极高 -- ✅ 复合索引(注意字段顺序:区分度高的放前面) CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC); -- ✅ 部分索引(只索引部分数据,节省空间) CREATE INDEX idx_active_users ON users(email) WHERE status = 'active'; -- ✅ GIN 索引(用于 JSONB / 数组字段) CREATE INDEX idx_metadata ON products USING GIN(metadata);
1.3 索引使用原则
| 场景 | 推荐索引类型 |
|---|---|
| —— | ————- |
| 精确匹配(=) | B-tree(默认) |
| 范围查询(>, <, BETWEEN) | B-tree |
| 模糊查询(LIKE ‘%xxx%’) | 不支持索引,考虑全文搜索 |
| 模糊查询(LIKE ‘xxx%’) | B-tree |
| 包含查询(JSONB) | GIN |
| 地理位置 | GiST / SP-GiST |
技巧2:查询优化——EXPLAIN 是你的好朋友
2.1 读懂执行计划
-- 开启 BUFFERS 可以看到 IO 情况 EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 12345; /* Seq Scan on orders (cost=0.00..153.00 rows=50 width=88) (actual time=0.020..1.200 rows=48 loops=1) Filter: (user_id = 12345) Rows Removed by Filter: 99952 Buffers: shared hit=128 */ -- ↑ 问题:Seq Scan(全表扫描),扫描了99952行才找到48行 -- 解决:给 user_id 建索引
2.2 常见慢查询优化
-- ❌ 慢查询:使用函数导致索引失效 SELECT * FROM users WHERE DATE(created_at) = '2024-01-01'; -- DATE() 函数让索引失效,全表扫描 -- ✅ 优化:用范围查询 SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02'; -- ❌ 慢查询:SELECT * 返回所有字段 SELECT * FROM orders WHERE user_id = 123; -- ✅ 优化:只查需要的字段 SELECT id, status, total_amount FROM orders WHERE user_id = 123; -- ❌ 慢查询:NOT IN 子查询 SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM banned_users); -- ✅ 优化:用 NOT EXISTS 或 LEFT JOIN SELECT u.* FROM users u LEFT JOIN banned_users b ON u.id = b.user_id WHERE b.user_id IS NULL;
技巧3:配置调优——不要只用默认值
3.1 关键参数(postgresql.conf)
# 内存配置(64GB 服务器示例) shared_buffers = 16GB # 物理内存的 25% effective_cache_size = 48GB # 物理内存的 75% work_mem = 64MB # 每个操作的内存,调大可加速排序/哈希 maintenance_work_mem = 2GB # VACUUM / CREATE INDEX 时的内存 # WAL 配置(影响写入性能) wal_buffers = 64MB # WAL 缓冲区 checkpoint_timeout = 30min # 检查点间隔,调大减少 IO checkpoint_completion_target = 0.9 # 分散 IO 压力 # 并发配置 max_connections = 200 # 根据连接池调整,不要设太大
3.2 用 pgTune 自动计算参数
# 在线工具:https://pgtune.leopard.in.ua/ # 或本地运行: docker run --rm pgtuned \ -c "db_type=web" \ -c "total_memory=64GB" \ -c "cpus=16" \ -c "connections=200"
技巧4:VACUUM 和 ANALYZE——维护表健康
-- 查看表膨胀情况
SELECT
schemaname,
relname AS table_name,
n_dead_tup,
n_live_tup,
round(n_dead_tup * 100.0 / (n_live_tup + n_dead_tup), 2) AS dead_percent
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY dead_percent DESC;
-- 手动 VACUUM(回收死元组空间)
VACUUM (VERBOSE, ANALYZE) your_table;
-- 设置自动 VACUUM(在 postgresql.conf)
autovacuum_max_workers = 6 # 同时运行的 autovacuum 进程数
autovacuum_vacuum_threshold = 100 # 触发 VACUUM 的最小更新行数
技巧5:分区表——应对亿级数据
-- 创建分区主表(按时间分区)
CREATE TABLE orders (
id BIGSERIAL,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2),
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
-- 创建分区(每月一个分区)
CREATE TABLE orders_2024_01 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders_2024_02 PARTITION OF orders
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- 查询时只扫描相关分区(分区裁剪)
SELECT * FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31';
-- ↑ 只扫描 orders_2024_01 分区,其他分区不扫描
技巧6:主从复制——读写分离
6.1 配置主库(Primary)
# postgresql.conf(主库) wal_level = replica # 开启 WAL 复制 max_wal_senders = 10 # 最大复制连接数 wal_keep_size = 1GB # 保留 WAL 文件大小
-- 创建复制用户 CREATE USER replicator REPLICATION LOGIN PASSWORD 'your_password'; -- pg_hba.conf 允许从库连接 # TYPE DATABASE USER ADDRESS METHOD host replication replicator 192.168.1.0/24 md5
6.2 配置从库(Replica)
# 从主库基础备份 pg_basebackup -h primary-host -D /var/lib/postgresql/data \ -U replicator -P -v -R # 启动从库(会自动以只读模式运行) pg_ctl start -D /var/lib/postgresql/data
-- 验证复制状态(在主库执行) SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn FROM pg_stat_replication; -- 在应用中实现读写分离 -- 写操作 → 主库 -- 读操作 → 从库(用连接池配置)
技巧7:连接池——减少连接开销
PostgreSQL 每个连接消耗约 10MB 内存,连接数过多会严重影响性能。
# 使用 PgBouncer 或 SQLAlchemy 连接池
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
engine = create_engine(
"postgresql://user:pass@localhost/mydb",
poolclass=QueuePool,
pool_size=20, # 连接池大小
max_overflow=10, # 超出 pool_size 后最多创建的连接数
pool_recycle=3600, # 连接回收时间(秒)
)
性能调优检查清单
□ 是否存在全表扫描的大表?(用 pg_stat_statements 查) □ 索引是否合理?(高区分度字段有索引,低区分度字段无冗余索引) □ 是否启用了 autovacuum? □ shared_buffers / effective_cache_size 是否按服务器内存调整? □ 是否有分区表应对大表? □ 是否配置了主从复制实现读写分离? □ 应用是否使用了连接池?
总结
| 优化层次 | 具体措施 | 预期效果 |
|---|---|---|
| ———- | ———- | ———- |
| SQL 层 | 优化慢查询、避免 SELECT * | 减少 50-90% 响应时间 |
| 索引层 | 合理建索引、删除冗余索引 | 查询提速 10-100 倍 |
| 配置层 | 调整 postgresql.conf | 提升并发处理能力 |
| 架构层 | 分区表 + 主从复制 | 支撑亿级数据 |
记住:先测瓶颈,再优化。用 EXPLAIN ANALYZE 和 pg_stat_statements 找到真问题。
👤 作者简介
一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~
博客:yunduancloud.icu
