由 John Doe 九月 5, 2025
在数据库性能优化领域,计划缓存(Plan Cache)的高效利用是提升 SQL 执行效率的关键环节之一。
特性提交日志
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_plan
或force_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