首页 / 后端开发 / PostgreSQL慢查询到毫秒级:

PostgreSQL慢查询到毫秒级:EXPLAIN、复合索引与VACUUM现场调优实战

Roxi
Roxi 加速器 — 稳定·快速·安全
全球节点覆盖,支持所有主流平台,一键连接无需配置。新用户免费试用。
立即体验 →

Chapter 1:OK so,先把慢SQL抓出来,不要靠猜!

方案A92方案B85方案C78方案D71方案E65

兄弟们开录!我是 eccfy,今天直接上屏幕:一张 orders 表 320 万行,接口从 1.8 秒卡到用户想砸键盘。接下来我们不玄学调参,先用官方内置工具定位。这个就是你搜“PostgreSQL性能调优教程”真正该看的第一步。

  1. 打开慢查询日志,先临时验证:
    ALTER SYSTEM SET log_min_duration_statement = '300ms';
    ALTER SYSTEM SET log_statement = 'none';
    SELECT pg_reload_conf();
  2. 装统计插件,生产环境强烈建议开:
    CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
    SELECT query, calls, mean_exec_time, rows
    FROM pg_stat_statements
    ORDER BY mean_exec_time DESC
    LIMIT 10;
  3. 抓到目标SQL后,别急着加索引,先跑执行计划:
    EXPLAIN (ANALYZE, BUFFERS)
    SELECT * FROM orders
    WHERE user_id = 8848
    AND status = 'paid'
    ORDER BY created_at DESC
    LIMIT 20;

Now watch this:屏幕上出现 Seq Scan,Buffers read 15432,Execution Time 1820ms。翻译成人话:数据库在扫大表,不是在查字典。这就是“PostgreSQL慢查询优化”的现场证据。

Chapter 2:接下来加索引,但别乱加,顺序决定生死

2020行业萌芽2021快速增长2022竞争加剧2023洗牌整合2024成熟稳定

很多同学搜“PostgreSQL索引怎么用”,结果一上来给每个字段单独建索引:user_id 一个、status 一个、created_at 一个。OK so,这在本例里不够爽,因为查询同时过滤、排序、分页。我们直接建复合索引:

CREATE INDEX CONCURRENTLY idx_orders_user_status_created
ON orders (user_id, status, created_at DESC);

注意我用了 CONCURRENTLY,线上建索引时它不会长时间阻塞写入;缺点是创建更慢,而且不能放在事务里。我实测这张 320 万行表,普通 CREATE INDEX 约 38 秒且有锁等待,CONCURRENTLY 约 71 秒,但业务写入没明显卡住。

再跑一次:

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE user_id = 8848
AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;

好,重点来了!执行计划从 Seq Scan 变成 Index Scan using idx_orders_user_status_created,Execution Time 从 1820ms 降到 43ms,Buffers read 从 15432 降到 87。弹幕可以打一个“起飞”。

如果你的查询只取少数字段,比如 id、amount、created_at,可以做覆盖索引,减少回表:

CREATE INDEX CONCURRENTLY idx_orders_cover
ON orders (user_id, status, created_at DESC)
INCLUDE (amount);

但别无脑堆索引。我的经验是:高频查询、选择性高、能服务排序的索引值得建;低基数字段比如 gender、boolean 单独建索引通常收益很差,还会拖慢 INSERT/UPDATE。

Chapter 3:验证修复:看时间、看缓存、看膨胀

最后做验收,这段就是“EXPLAIN ANALYZE教程”的精华版。你至少检查三项:

  • 执行时间Execution Time 是否稳定下降,例如 1800ms → 50ms 内。
  • 扫描方式:是否从 Seq Scan 变成 Index ScanBitmap Index Scan
  • 命中率:用下面SQL看缓存是否健康:
    SELECT sum(heap_blks_hit) * 100.0 /
    NULLIF(sum(heap_blks_hit + heap_blks_read),0) AS cache_hit_ratio
    FROM pg_statio_user_tables;

如果表频繁更新删除,再补一刀维护:

VACUUM (ANALYZE) orders;
SELECT relname, n_dead_tup
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;

我的验收标准:接口 P95 延迟连续 15 分钟低于 100ms,慢查询日志不再刷同一条SQL,pg_stat_statements.mean_exec_time 明显下降。这样才算真的修好,不是心理安慰。

如果你需要查海外官方资料,免费路线可以先用官方文档和社区问答;网络不稳定时,免费VPN也能临时用但速度和隐私不可控,付费工具只是选项之一,比如 Roxi:wizzegroup.com。觉得这期有用,收藏,评论区丢你的慢SQL,咱们下期现场拆!