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 认为是这种情况,它将从那时起使用通用计划。
在你的情况下,这种启发式方法似乎出错了,通用计划实际上效率较低。您可以判断数据库会话中的前五次执行是否很快,但第六次执行是否很慢。在这种情况下,请尝试以下操作:
尝试提供更好的统计数据:
ALTER TABLE tab SET (autovacuum_analyze_scale_factor = 0.01);
ANALYZE tab;
然后启动一个新的数据库会话并重试。也许经常收集 table 统计数据已经解决了问题。
如果这还不够,请禁用该功能的通用计划:
ALTER FUNCTION my_variable_func SET plan_cache_mode = force_custom_plan;
那么 PostgreSQL 将永远不会再为该函数使用通用计划。
我已经编写了一个 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 认为是这种情况,它将从那时起使用通用计划。
在你的情况下,这种启发式方法似乎出错了,通用计划实际上效率较低。您可以判断数据库会话中的前五次执行是否很快,但第六次执行是否很慢。在这种情况下,请尝试以下操作:
尝试提供更好的统计数据:
ALTER TABLE tab SET (autovacuum_analyze_scale_factor = 0.01); ANALYZE tab;
然后启动一个新的数据库会话并重试。也许经常收集 table 统计数据已经解决了问题。
如果这还不够,请禁用该功能的通用计划:
ALTER FUNCTION my_variable_func SET plan_cache_mode = force_custom_plan;
那么 PostgreSQL 将永远不会再为该函数使用通用计划。