Postgres 用户定义函数行为异常

Postgres User Defined Function acting strange

我已经编写了一个 SQL 查询,我想将其包装在一个函数中。它收集两个时间戳之间的数据,并将其 returns 提供给用户。使用提供的时间戳会极大地降低函数的速度,而使用硬编码到查询中的相同时间戳则运行良好。

知道可能发生的事情吗?

第一个函数:

CREATE OR REPLACE FUNCTION my_variable_func(
   id text,
   start_time timestamp with time zone DEFAULT '2022-05-24 07:10:00',
   end_time timestamp with time zone DEFAULT '2022-05-24 07:20:00')
RETURNS TABLE(... some stuff...) 
LANGUAGE 'sql'
COST 100
VOLATILE PARALLEL SAFE 
ROWS 1000

AS $BODY$
SELECT 
  ... some complex CTE + window query + aggregate ...
WHERE event_time > ::timestamp with time zone
AND event_time < ::timestamp with time zone

快速硬编码查询:

CREATE OR REPLACE FUNCTION my_hardcoded_func(
   id text,
   start_time timestamp with time zone DEFAULT '2022-05-24 07:10:00',
   end_time timestamp with time zone DEFAULT '2022-05-24 07:20:00')
RETURNS TABLE(... some stuff...) 
LANGUAGE 'sql'
COST 100
VOLATILE PARALLEL SAFE 
ROWS 1000

AS $BODY$
SELECT 
  ... some complex CTE + window query + aggregate ...
WHERE event_time > '2022-05-24 07:10:00'::timestamp with time zone
AND event_time < '2022-05-24 07:20:00'::timestamp with time zone

运行

SELECT * FROM my_variable_func(123, '2022-05-24 07:10:00','2022-05-24 07:20:00')

30 秒内完成,而

SELECT * FROM my_hardcoded_func(123, '2022-05-24 07:10:00','2022-05-24 07:20:00')

不到 2 秒。有任何想法吗?我难住了...

编辑

经过更多的工作: 查看数据库上的锁;看起来“硬编码”函数从正确的分区获取数据,而“变量”函数搜索所有分区以获取它需要的数据,这非常慢。我不确定发生了什么变化,因为 运行 变量函数花费的时间超过 30 秒。我试图通过“set enable_seqscan = off;”强制使用索引。没有成功。

这很可能是计划缓存的影响。 PostgreSQL 可以缓存 PL/pgSQL 语句中查询的执行计划。通常,这是一个胜利,因为查询不必在每次执行时都进行规划。但有时事情会出错。

您在数据库会话中调用 my_variable_func 的前五次,它会生成一个 自定义计划,该计划将实际参数值考虑在内。从第六次执行开始,它将检查 通用计划 (不考虑实际参数值的计划)是否承诺与自定义计划一样高效。如果 PostgreSQL 认为是这种情况,它将从那时起使用通用计划。

在你的情况下,这种启发式方法似乎出错了,通用计划实际上效率较低。您可以判断数据库会话中的前五次执行是否很快,但第六次执行是否很慢。在这种情况下,请尝试以下操作:

  1. 尝试提供更好的统计数据:

    ALTER TABLE tab SET (autovacuum_analyze_scale_factor = 0.01);
    ANALYZE tab;
    

    然后启动一个新的数据库会话并重试。也许经常收集 table 统计数据已经解决了问题。

  2. 如果这还不够,请禁用该功能的通用计划:

    ALTER FUNCTION my_variable_func SET plan_cache_mode = force_custom_plan;
    

    那么 PostgreSQL 将永远不会再为该函数使用通用计划。