PostgreSQL 19: pg_stat_statements 新增通用和自定义计划计数器

John Doe 九月 5, 2025

在数据库性能优化领域,计划缓存(Plan Cache)的高效利用是提升 SQL 执行效率的关键环节之一。

image

特性提交日志

pg_stat_statements:新增通用计划与自定义计划计数器。

此补丁为 pg_stat_statements 新增了两个计数器:

  • generic_plan_calls(通用计划调用次数)
  • custom_plan_calls(自定义计划调用次数)

这两个计数器分别用于跟踪预编译语句(prepared statement)使用通用计划(generic plan)和自定义计划(custom plan)执行的次数。pg_prepared_statements 的数据仅局限于单个会话,而新增计数器则在查询级别(涵盖顶层和非顶层查询)提供了与 pg_prepared_statements 功能等效的全局统计数据。

讨论:https://postgr.es/m/CAA5RZ0uFw8Y9GCFvafhC=OA8NnMqVZyzXPfv_EePOt+iv1T-qQ@mail.gmail.com

计划缓存:通用计划和自定义计划

当 PostgreSQL 执行预编译语句(Prepared Statement)时,会根据参数特性生成两种类型的执行计划:

  • 通用计划(Generic Plan):不依赖具体参数值的参数化计划,生成一次后可重复使用,适合参数值分布均匀、执行逻辑固定的场景,能减少计划生成开销。
  • 自定义计划(Custom Plan):根据每次执行的具体参数值动态生成的计划,可利用参数对应的表数据分布(如索引选择、过滤条件效率)生成更优执行路径,但每次执行需重新计算计划,消耗额外 CPU 资源。

PostgreSQL 默认会根据语句调用次数、参数分布等因素自动选择计划类型,也可通过plan_cache_mode参数强制指定(如force_generic_planforce_custom_plan)。

本次提交在pg_stat_statements视图中,新增以下两个bigint类型字段,用于跟踪预编译语句的计划使用次数:

  • generic_plan_calls:该语句通过通用计划执行的总次数。
  • custom_plan_calls:该语句通过自定义计划执行的总次数。

这两个字段与pg_stat_statements原有的calls(总执行次数)字段存在逻辑关系:calls = generic_plan_calls + custom_plan_calls(仅针对预编译语句,非预编译语句的两个新字段值为 0)。

示例

新特性的价值主要体现在性能调优的实践中。以下是几个典型应用场景,可帮助 DBA 精准优化计划缓存策略。

判断高频语句的计划选择合理性

对于高频执行的预编译语句,可通过新字段分析计划类型是否最优。例如:

-- 查看执行次数 Top 10 的预编译语句,及其计划使用情况
SELECT 
  queryid,
  query,
  calls,
  generic_plan_calls,
  custom_plan_calls,
  -- 计算通用计划占比
  ROUND(generic_plan_calls::NUMERIC / calls * 100, 2) AS generic_plan_ratio,
  -- 关联执行效率数据
  mean_exec_time,
  rows
FROM pg_stat_statements
-- 筛选预编译语句(非预编译语句的两个新字段为 0)
WHERE generic_plan_calls + custom_plan_calls > 0
ORDER BY calls DESC
LIMIT 10;

分析逻辑

  • generic_plan_ratio接近 100%,但mean_exec_time明显高于同类语句:可能通用计划未利用最优索引,可尝试强制自定义计划(SET plan_cache_mode = force_custom_plan)。
  • generic_plan_ratio接近 0%,且calls极高(如百万级):自定义计划的重复生成可能消耗大量 CPU,可尝试强制通用计划并验证执行效率。

验证计划缓存参数调整效果

当调整plan_cache_mode参数后,可通过新字段验证调整效果:

SELECT pg_stat_statements_reset() IS NOT NULL AS t;
PREPARE p1 AS SELECT $1 AS a;
EXECUTE p1(1);

-- 调整前记录基准数据
SELECT calls, generic_plan_calls, custom_plan_calls, query
  FROM pg_stat_statements;
 calls | generic_plan_calls | custom_plan_calls |                       query                        
-------+--------------------+-------------------+----------------------------------------------------
     1 |                  0 |                 1 | PREPARE p1 AS SELECT $1 AS a
     1 |                  0 |                 0 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
(3 rows)

-- 强制使用通用计划
SET plan_cache_mode = force_generic_plan;
-- 执行预编译语句
EXECUTE p1(100);

-- 查看调整后计数器变化
SELECT calls, generic_plan_calls, custom_plan_calls, query
  FROM pg_stat_statements;
 calls | generic_plan_calls | custom_plan_calls |                       query                        
-------+--------------------+-------------------+----------------------------------------------------
     2 |                  1 |                 1 | PREPARE p1 AS SELECT $1 AS a
     1 |                  0 |                 0 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
     1 |                  0 |                 0 | SET plan_cache_mode TO $1
(3 rows)

非常不错的体验,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/3357471cf9f5e470dfed0c7919bcf31c7efaf2b9