首页 > Technology > 正文

PostgreSQL 慢 SQL 定位实战手册

fenij 2026-09-14 5 Technology

数据库突然变慢,十有八九是几条慢 SQL 在拖后腿。与其对着监控图瞎猜,不如直接打开 PostgreSQL 自带的 pg_stat_statements 扩展——它能把每条 SQL 的执行次数、总耗时、平均耗时、读写块数都记下来,三句查询就能把“元凶”按耗时排好队。本文用一套可复制的命令,带你从零启用到精准定位慢查询。

一、先确认扩展已加载

pg_stat_statements 不是开箱即用的视图,而是一个需要预加载的共享库。最易踩的坑就是只建了扩展却没在配置文件里登记,结果查询报错。先检查 postgresql.conf

# postgresql.conf 必须包含下面这行,且只能在启动时生效
shared_preload_libraries = 'pg_stat_statements'

# 改完配置要重启,仅 reload 不生效
pg_ctl restart -D /var/lib/pgsql/15/data

# 进入 psql 后建扩展
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

确认是否加载成功,查一眼视图是否存在即可:SELECT count(*) FROM pg_stat_statements; 能返回 0 行(说明已就绪,只是还没统计)就对了。

二、三句 SQL 把慢查询揪出来

定位慢 SQL 有三个常用排序维度,对应不同排查场景,别只会按总耗时排:

排序维度 适用场景 ORDER BY 字段
总耗时 吃资源的大户、整体变慢 total_exec_time DESC
平均耗时 单次就很慢的尖刺 SQL mean_exec_time DESC
读写块数 全表扫、缺索引的 I/O 杀手 (shared_blks_read+shared_blks_written) DESC

最常用的“按总耗时排”查询如下,注意时间字段单位是毫秒:

SELECT query,
       calls,
       round(total_exec_time / 1000, 1)  AS total_s,
       round(mean_exec_time  / 1000, 1)  AS mean_ms,
       rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

如果怀疑是某条“单次就很慢”的 SQL(比如报表导出的偶发卡顿),把 ORDER BY 换成 mean_exec_time DESC,往往能挖出被总耗时掩盖的尖刺。

三、看 I/O 与缓存命中率

慢不一定在 CPU,也可能在磁盘。下面这句按读写块数排序,专门找全表扫描、缺索引的语句;同时算缓存命中率,命中率低于 95% 就要警惕:

SELECT query,
       calls,
       round((shared_blks_read + shared_blks_written) / 1024.0, 1) AS mb,
       100.0 * shared_blks_hit
           / nullif(shared_blks_hit + shared_blks_read, 0)        AS hit_pct
FROM pg_stat_statements
ORDER BY shared_blks_read + shared_blks_written DESC
LIMIT 20;
⚙️
pg_stat_statements.max
PG13+ 默认 5000 条,超了丢弃最少执行的

🔄
track = top
默认只统计客户端直发语句,函数内不计入

🧹
reset 再观察
SELECT pg_stat_statements_reset(); 清空后重采更准

踩坑实录

上周一台 PG15 自建库,我照文档在 postgresql.conf 加了 shared_preload_libraries = 'pg_stat_statements',图省事只执行了 pg_ctl reload,没重启。然后 CREATE EXTENSION 直接报 ERROR: pg_stat_statements must be loaded via shared_preload_libraries。我一开始以为是扩展没装 contrib 包,折腾半小时重装 rpm,还是同样的错。最后才反应过来:这个参数只能启动时加载,reload 不生效。补了一句 pg_ctl restart -D /var/lib/pgsql/15/data,扩展秒建成功,视图立刻能查。教训——改完共享库参数,永远先 restart 再建扩展,别想走捷径。

四、拿到慢 SQL 后下一步做什么

定位到具体 SQL 只是第一步,真正的优化要靠执行计划:

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 123 AND created_at > '2026-01-01';

重点看 Seq Scan(全表扫描,通常意味着缺索引)、actual timerows 是否严重不符(统计信息过期,跑 ANALYZE 表名;)。一个常见补丁是在过滤列上加联合索引。

⚠️ 版本坑:PostgreSQL 13 把视图里的 total_time / mean_time 改名成了 total_exec_time / mean_exec_time。如果你抄的是老教程(12 及以前),查询会报 column total_time does not exist。用 SELECT version(); 确认大版本再选字段名。

常见问题

Q:托管云数据库(阿里云 RDS / 腾讯云)也要改 postgresql.conf 吗?
A:不能直接改配置文件。shared_preload_libraries 要在控制台“参数设置”里加 pg_stat_statements 并重启实例,之后 CREATE EXTENSION 即可,扩展本身一般已预装。

Q:pg_stat_statements 视图是空的,怎么办?
A:先确认扩展已建(\dx 能看到),再确认配置里确实预加载了且实例已重启;若之前只 reload,补一次重启即可。

Q:统计会一直累积吗,要不要定期清?
A:会一直累积到 max 上限。排查某段时间的问题时,先 pg_stat_statements_reset() 清空再观察,数据更聚焦。

如果你在定位慢 SQL 时遇到其他报错,欢迎在 fenij.com 留言,一起把排障过程补完整。

相关完整手册

系统化的排查与配置思路,建议顺手收藏这几篇完整手册: