PostgreSQL 诊断老兵:用四个 GUC 拆开语句的 CPU、缺页与上下文切换

2026-09-06 36 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

PostgreSQL 的 log_parser_statslog_planner_statslog_executor_statslog_statement_stats 是一组非常早期、也很少被日常提起的诊断参数。它们不会告诉你完整的执行计划,也不是现代性能分析器的替代品;但当你需要回答“这条语句到底慢在解析、规划,还是执行”,并进一步观察 CPU 时间、页面错误和上下文切换时,它们仍然有独特价值。

四个参数分别看什么

这几个参数通过 PostgreSQL 的统一配置系统(GUC)控制,并把资源统计写入服务器日志。它们关注的是操作系统层面的进程开销,而不是 SQL 结果本身。

  • log_parser_stats:记录解析阶段的资源使用情况。
  • log_planner_stats:记录规划阶段的资源使用情况。
  • log_executor_stats:记录执行阶段的资源使用情况。
  • log_statement_stats:记录整条语句的累计资源使用情况。

“解析”“规划”和“执行”是不同的阶段。一个查询可能执行计划很简单,却在规划阶段消耗了大量 CPU;也可能规划很快,但真正读取数据、排序或等待资源的执行阶段成本很高。分阶段日志可以帮助你把这两类问题分开。

典型统计信息可能包括用户态和内核态 CPU 时间、经过时间、页面错误、上下文切换,以及部分系统 I/O 计数。具体字段和可见程度取决于 PostgreSQL 版本、操作系统和构建方式,因此不应把某一行日志格式当成跨版本接口。

为什么它们仍然有用

现代 PostgreSQL 调优通常从 EXPLAIN (ANALYZE, BUFFERS)pg_stat_statements 和系统监控开始。这些工具分别擅长执行计划、语句聚合和长期趋势,但它们未必能直接回答以下问题:

  • 查询文本相同,为什么某次请求的系统开销明显更高?
  • 规划阶段是否因为复杂表达式、分区表或大量候选路径而变重?
  • 执行时间之外,进程是否产生了异常多的页面错误或上下文切换?
  • 一条语句的总成本,主要来自哪个 PostgreSQL 内部阶段?

这组参数的特点是“粗粒度但贴近进程”。它不会替你解释每个计划节点,却能提供一个低层次的成本轮廓。比如,log_planner_stats 的 CPU 时间明显升高,而 log_executor_stats 并没有同步增加,就值得检查规划复杂度、统计信息、分区数量和连接关系,而不是立刻去调整缓存或磁盘参数。

可以这样实践:只在诊断会话中打开

这些参数会产生额外日志,通常不适合长期在生产环境全局开启。更稳妥的方式是使用一个受控的诊断会话,并确保当前角色有修改相应 GUC 的权限。

下面的命令假设你已经连接到目标数据库。先确认参数是否存在以及当前值:

SHOW log_statement_stats;
SHOW log_parser_stats;
SHOW log_planner_stats;
SHOW log_executor_stats;

如果服务器允许在会话级别修改,可以只对当前连接开启:

SET log_statement_stats = on;
SET log_parser_stats = on;
SET log_planner_stats = on;
SET log_executor_stats = on;

SELECT count(*)
FROM public.orders
WHERE customer_id = 42;

RESET log_statement_stats;
RESET log_parser_stats;
RESET log_planner_stats;
RESET log_executor_stats;

随后到 PostgreSQL 服务器日志中查找该会话产生的统计记录。为了避免把其他请求混在一起,建议给诊断连接设置一个容易搜索的应用名:

psql "$DATABASE_URL" \
  -v ON_ERROR_STOP=1 \
  -c "SET application_name = 'guc-stats-investigation';" \
  -c "SET log_statement_stats = on;" \
  -c "SET log_parser_stats = on;" \
  -c "SET log_planner_stats = on;" \
  -c "SET log_executor_stats = on;" \
  -c "SELECT count(*) FROM public.orders WHERE customer_id = 42;"

如果日志写入 systemd journal,可以这样筛选:

journalctl -u postgresql --since "10 minutes ago" \
  | rg "guc-stats-investigation|STATEMENT|PARSER|PLANNER|EXECUTOR"

不同发行版可能把日志写入文件而不是 journal。此时应根据 SHOW log_directory;SHOW log_filename; 和发行版的日志配置定位文件。不要只看一条样本就下结论,最好在相同参数、相同数据规模和相似缓存状态下重复执行多次。

读日志时要注意的边界

这些统计是进程级、阶段级的观测值,不等于完整的 SQL 性能报告。页面错误可能与操作系统内存映射有关,不应简单等同于“发生了一次慢磁盘读取”;上下文切换也可能来自调度、锁等待或其他系统活动。它们更适合用来发现异常方向,再结合其他工具确认原因。

可以把它们和下面几类证据放在一起看:

  • EXPLAIN (ANALYZE, BUFFERS) 检查计划节点、实际行数、共享缓冲区命中和读取。
  • pg_stat_statements 观察同一类语句在较长时间内的平均时间、调用次数和总成本。
  • 用操作系统工具观察 CPU、内存、I/O 和调度情况。
  • 通过日志中的时间、进程标识和 application_name,把资源统计与具体请求对应起来。

另一个现实限制是权限和构建配置。某些环境会限制这些参数的修改,某些 PostgreSQL 构建或版本也可能提供不同程度的支持。连接目标实例后先执行 SHOW,并在测试环境验证日志格式和权限,不要假设所有服务器都能直接执行上述设置。

一份实用检查清单

遇到一条“看起来只是偶尔变慢”的语句时,可以按这个顺序使用:

  1. 先用 pg_stat_statements 确认问题是否稳定复现,排除只看单次请求的误判。
  2. EXPLAIN (ANALYZE, BUFFERS) 检查执行计划和实际数据访问。
  3. 在隔离的诊断会话中短时间开启四个统计参数。
  4. 对比解析、规划、执行和整条语句的资源开销。
  5. 再结合系统监控、锁信息和日志时间线确认根因。
  6. 诊断完成后立即关闭参数,并清理可能包含敏感查询文本的日志。

这四个 GUC 不适合当作常驻监控指标,也不能替代执行计划分析。但当问题落在“数据库内部哪个阶段消耗了进程资源”这个层面时,它们提供了一种简单、直接且难以被高层指标完全替代的视角。关键在于短时间、可重复、带上下文地使用它们,而不是把日志数量本身当成诊断结果。


相关推荐