导读:本期聚焦于高建功创作的《PostgreSQL慢查询优化:pg_stat_monitor增强维度如何定位瓶颈?》,敬请观看详情。针对PostgreSQL慢查询,常规的pg_stat_statements往往只能看到总耗时、调用次数和平均时间,执行计划版本、等待事件、CPU与IO时间占比等关键信息缺失。pg_stat_monitor在这套统计基础上增加了更多观察维度,包括执行计划哈希、等待事件分类、时间桶聚合、实际返回与扫描行数、计划时间和执行时间拆分等。当一条SQL突然变慢时,通过时间桶可以对比变慢前后各维度的差异,借助等待事件能分辨是锁竞争、磁盘IO还是CPU计算,利用执行计划哈希能确认是否发生了计划跳变。本文介绍扩展的安装配置、关键指标含义,以及结合bucket、wait event和plan_hash的排查SQL,帮助从多维数据中快速锁定慢查询根因,避免盲目调整参数或索引。

PostgreSQL 的慢查询诊断通常从 pg_stat_statements 开始,但它只回答哪条 SQL 总耗时最长,很难解释为什么同一条 SQL 会突然从几十毫秒飙升到几秒。改表结构、加索引、调参数之前,需要先知道到底是执行计划变了、等待事件集中还是硬件资源紧张。pg_stat_monitor 在保留归一化统计的基础上,把时间桶、执行计划哈希、等待事件、CPU 时间与 IO 指标放进同一张表里,让慢查询定位从单点时间值变成多维度对比。

PostgreSQL慢查询优化:pg_stat_monitor增强维度如何定位瓶颈?

一、pg_stat_monitor 与 pg_stat_statements 的核心差异

很多人第一反应是它与 pg_stat_statements 有什么区别。简单说,后者擅长回答哪些查询累计消耗最高,前者则进一步回答某个时间段内发生了什么。它的统计以 bucket 为粒度,每个 bucket 默认 60 秒,可以按时间切片观察调用次数、总耗时、平均耗时、影响行数等指标的变化。当一条 SQL 在某个时间点开始变慢时,直接对比前后两个 bucket 的数据,比看一条全局平均值更有意义。

另一个关键差异是 plan_hash。查询文本归一化后相同的 SQL,只要执行计划发生变化,就会生成不同的 plan_hash。很多性能劣化来自统计信息过期或参数倾斜导致的计划跳变,这一维度能直接确认。再加上 wait_event_type 和 wait_event,例如大量 ClientRead 可能说明应用消费结果慢,而 DataFileRead 则指向磁盘 IO。这些信息在原生 pg_stat_statements 中都没有。

下面列出两者在几个关键点上的差别:

  • 时间粒度:pg_stat_statements 仅聚合累计值,pg_stat_monitor 支持 bucket 时间序列。
  • 执行计划:pg_stat_statements 不记录,pg_stat_monitor 通过 plan_hash 标识计划版本。
  • 等待事件:pg_stat_statements 无,pg_stat_monitor 记录等待事件类型与名称。
  • 资源拆分:pg_stat_monitor 增加 cpu_user_time、cpu_sys_time、shared_blks_read 等指标。

因此,当需要寻找变慢原因而不是选择 Top SQL 时,pg_stat_monitor 提供的信息密度更高。代价是它需要额外内存保存 bucket 历史数据,必须根据实例负载合理设置保留数量和长度。

二、安装与关键配置:数据粒度由参数决定

安装 pg_stat_monitor 通常在编译或使用二进制包后,在 postgresql.conf 中把它加入 shared_preload_libraries。因为统计收集需要挂到查询执行器上,必须随服务启动加载,不能只通过 CREATE EXTENSION 动态加载。修改配置后需要重启实例,再在目标库执行扩展创建。

-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements, pg_stat_monitor'

-- 在数据库中创建扩展
CREATE EXTENSION IF NOT EXISTS pg_stat_monitor;

关键参数中,pgsm_bucket_time 控制每个时间桶的秒数,默认 60 秒。对于高频交易类系统,可以调小到 10 或 15 秒,代价是数据量增大;对于报表库,60 秒通常足够。另一个重要参数是 pgsm_max_buckets,它限制每个归一化查询保留多少个 bucket,默认 10。若需要回溯更长时间,应适当调大,但会增加内存消耗。使用 pg_stat_monitor_settings 函数可以确认生效值。

还需要关注 pgsm_enable_query_plan,开启后会记录查询执行计划文本,方便直接查看当时使用的计划。执行计划可能很长,生产环境建议只对排查阶段开启,或者配合 pgsm_max_plan_text 限制长度。另一个参数 pgsm_normalized_query 默认开启,会把常量替换为 $1,避免因为输入值不同而拆成大量条目。

配置完成后,通过 SHOW shared_preload_libraries; 确认加载顺序。加载失败时查看日志中的 pg_stat_monitor 相关错误,常见原因是扩展未编译到当前 PostgreSQL 大版本,需要安装对应版本包。

三、核心增强维度拆解:bucket、plan_hash 与 wait_event

先看 bucket。它把统计数据按固定时间片切开,表结构中的 bucket 表示该条记录属于哪个编号的桶,bucket_start_time 给出桶的起始时间。对比故障窗口前后的 bucket,能快速发现 calls 是否突增、total_exec_time 是否异常、shared_blks_read 与 temp_blks_written 是否同步恶化。例如某个查询在某个时间点后每次执行时间变长,但调用次数没变,更多指向资源或计划问题,而不是流量增加。

再看 plan_hash。同一个归一化查询如果优化器选择不同关联顺序或访问路径,这个哈希值会改变。要定位计划跳变,可以按 query 分组后查看是否出现多个 plan_hash。发现计划变化后,最直接的修复是重新收集相关表统计信息,或调整 random_page_cost 等开销参数让优化器回到预期路径。

然后是 wait_event_type 与 wait_event。当查询被锁阻塞时,等待类型会成为 Lock,事件名如 transactionid 或 tuple;当等待数据文件读取时,会出现 IO 和 DataFileRead。把它们和 bucket 放在一起观察,可以看到某一时刻是否发生大面积锁等待,或者存储 IO 延迟突然升高。这比单纯分析 SQL 文本更能反映外部环境变化。

以下 SQL 可以从这三个维度对某条查询做初步画像:

SELECT bucket_start_time,
       calls,
       total_exec_time / calls AS avg_exec_ms,
       plan_hash,
       wait_event_type,
       wait_event,
       shared_blks_hit,
       shared_blks_read,
       temp_blks_written
FROM pg_stat_monitor
WHERE query ~* 'FROM orders'
  AND bucket_start_time >= now() - interval '1 hour'
ORDER BY bucket_start_time DESC;

实际使用中建议先用 query 字段模糊匹配定位目标 SQL,再使用 queryid 精确过滤。由于 query 文本可能很长,输出时截断即可。若只看某一类等待事件,直接在 WHERE 中增加条件。

四、优化实战:从多维数据定位根因

假设一条订单汇总 SQL 在业务高峰期突然变慢,pg_stat_statements 显示总耗时上升,但无法判断原因。第一步按时间桶查看该 SQL 的 mean_exec_time 与 calls,确认是从某个时间点开始劣化,还是随调用量线性增加。如果 calls 基本不变但平均耗时骤增,继续看 plan_hash 是否改变。

第二步,如果发现两个 plan_hash 对应不同时间桶,说明执行计划发生跳变。此时可以从扩展保存的计划文本取出旧计划与新计划对比,也可以在测试库用相同参数分别 EXPLAIN (ANALYZE, BUFFERS) 模拟。常见原因是统计信息过期后优化器低估行数,错误选择嵌套循环而不是哈希关联。此时执行 ANALYZE orders 或扩大相关列的 n_distinct 统计值,往往能恢复。

第三步,如果 plan_hash 没变,就重点看 wait_event_type。例如大量 IO 等待与 temp_blks_written 上升,说明查询临时文件增多,可能因为 work_mem 不够,排序或哈希溢出到磁盘。适当调大当前会话的 work_mem 并重试,能明显缩短时间。如果等待事件以 Lock 为主,则需要抓取阻塞链路,优化长事务或调整更新顺序。

还有一个常被忽略的维度是 CPU 时间拆分。cpu_user_time 与 cpu_sys_time 可以帮助区分 CPU 消耗与等待消耗。假设总执行时间很高,但 CPU 时间占比很低,说明大部分时间在等待,应从锁、IO、网络等方向排查;反之,CPU 时间占比接近总时间,说明计算本身重,需要改写 SQL 或增加过滤条件。

最后给出两个常用诊断 SQL。查找执行计划在 30 分钟内发生变化的查询:

SELECT query,
       count(DISTINCT plan_hash) AS plan_versions,
       max(calls) AS max_calls,
       max(total_exec_time) AS max_exec_time
FROM pg_stat_monitor
WHERE bucket_start_time > now() - interval '30 minutes'
GROUP BY query
HAVING count(DISTINCT plan_hash) > 1
ORDER BY max_exec_time DESC;

检测最近一小时内等待事件分布:

SELECT wait_event_type,
       wait_event,
       count(*) AS bucket_count,
       sum(total_exec_time) AS total_time
FROM pg_stat_monitor
WHERE bucket_start_time >= date_trunc('hour', now()) - interval '1 hour'
  AND bucket_start_time < date_trunc('hour', now())
GROUP BY wait_event_type, wait_event
ORDER BY total_time DESC;

排查完成后,可以调用 SELECT pg_stat_monitor_reset(); 清空历史统计,避免旧数据干扰下一次分析。持续优化时,建议把关键指标接入监控系统,当 mean_exec_time 分位数或等待事件出现异常时自动告警,而不是等用户反馈才介入。

PostgreSQL慢查询优化pg_stat_monitor查询性能监控修改时间:2026-10-06 15:44:21

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1006/66505.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。