pg_stat_statements默认不统计执行时间,需同时开启track_io_timing=on且pg_stat_statements.track设为'all'或'top',否则total_time和mean_time恒为0;修改后须重启数据库。

pg_stat_statements 默认不统计执行时间,必须开启 track_io_timing = on 且确保 pg_stat_statements.track 包含 'all' 或 'top',否则 total_time 和 mean_time 始终为 0。
确认 pg_stat_statements 已正确启用并采集耗时数据
很多情况下查不到耗时,根本原因是没开底层计时开关:
-
track_io_timing必须设为on(默认off),否则total_time永远是 0 —— 这个参数控制是否测量语句级 CPU + I/O 时间,不是可选优化项 -
pg_stat_statements.track推荐设为'all',避免漏掉 PL/pgSQL 函数内嵌的 SQL;若只设'top',函数体内的语句不会被单独统计 - 修改后需重启 PostgreSQL(
track_io_timing是 superuser-only 的 postmaster 参数,不能SET动态生效) - 验证是否生效:
SELECT * FROM pg_stat_statements LIMIT 1;看total_time列是否非零
查最耗时 SQL:按 total_time 还是 mean_time?
直接 ORDER BY total_time DESC LIMIT 10 容易误判 —— 一条慢但只执行 1 次的 SQL 可能不如一条快但执行上万次的 SQL 对整体负载影响大:
- 用
total_time找「总资源吞噬者」:适合定位拖慢整个实例的语句,例如某报表 SQL 单次跑 30 秒、每天跑 2 次,total_time就是 60 秒 - 用
mean_time找「单次性能瓶颈」:适合发现低效写法,比如本该走索引却全表扫描,哪怕只执行 1 次,mean_time也会异常高 - 务必结合
calls看频次:如果mean_time高但calls = 1,优先检查是否为偶发问题(如锁等待、临时磁盘不足)
还原原始 SQL 时要注意 normalized_queryid 和 query 字段的区别
pg_stat_statements 会把字面量参数自动替换为 、,形成归一化语句(query 字段),但同一类查询可能因参数不同导致执行计划差异:
-
query是归一化后的模板,例如SELECT * FROM users WHERE id = $1;—— 用于聚合计数,但看不出实际执行了哪些具体值 - 想定位某次真实慢查询,得结合
pg_stat_activity或日志:开启log_min_duration_statement = 1000,再用query_id关联pg_stat_statements中的queryid -
queryid是哈希值,不可逆;不要试图靠它反推原始 SQL,只能靠日志或应用层打点对齐
常见干扰项和性能陷阱
即使数据看起来合理,也可能被以下情况误导:
- 事务中多个 SQL 共享一次
total_time计算?否 ——pg_stat_statements统计粒度是单条语句,不是事务 - VACUUM / ANALYZE 会被统计吗?会,但它们本身不进
pg_stat_statements(属于内部命令),只有显式发出的 SQL 才计入 - 连接池(如 pgbouncer)会影响统计吗?会 —— 如果用事务池模式(transaction pooling),
queryid无法准确对应到应用端真实调用,因为语句在池中被复用和重写 - 扩展版本不匹配:9.4+ 自带,但 13+ 新增
blk_read_time/blk_write_time,若用旧客户端连新服务,可能读不到这些字段
真正难的不是排序取 top 10,而是判断哪条 SQL 的耗时变化是计划劣化、参数倾斜、还是统计本身被缓存/连接池扭曲了。别只盯着数字,先确认数据来源干净,再看趋势。










