PostgreSQL 函数内部的智能逻辑查询性能
Posted
技术标签:
【中文标题】PostgreSQL 函数内部的智能逻辑查询性能【英文标题】:Smart logic queries performance inside functions for PostgreSQL 【发布时间】:2017-05-05 10:28:06 【问题描述】:考虑以下 sql 查询:
SELECT a,b,c
FROM t
WHERE (id1 = :p_id1 OR :p_id1 IS NULL) AND (id2 = :p_id2 OR :p_id2 IS NULL)
Markus Winand 在他的“SQL Performance explained”一书中将这种方法称为所有性能最差的反模式之一,并解释了原因(数据库必须为最坏的情况准备计划当所有过滤器都被禁用时)。
但后来他也写道,对于 PostgreSQL,这个问题只有在重新使用语句 (PreparedStatement
) 句柄时才会出现。
现在还假设上面的查询被包装到函数中,例如:
CREATE FUNCTION func(IN p_id1 BIGINT,IN p_id2 BIGINT)
...
$BODY$
BEGIN
...
END;
$BODY$
到目前为止,我对几点有误解:
在函数包装的情况下还会出现这个问题吗? (我试图查看函数调用的执行计划,但即使使用SET auto_explain.log_nested_statements = ON
,Postgres 也不会向我显示内部函数调用的详细信息)。
假设我正在处理遗留项目,不能更改函数本身,只能更改 java 执行代码。在这里避免准备好的语句并每次使用动态查询会更好吗? (假设执行时间很长,最多几秒钟)。说这个,大概是丑的做法:
getSession().doWork(connection ->
ResultSet rs = connection.createStatement().executeQuery("select * from func("+id1+","+id2+")");
...
)
【问题讨论】:
【参考方案1】:1。 视情况而定。
当不使用准备好的语句时,PostgreSQL 每次都使用参数值重新计划一次查询。它被称为自定义计划。
使用准备好的语句(你是对的,PL/pgSQL 函数确实使用准备好的语句)它更复杂。 PostgreSQL 准备语句(解析其文本并存储解析树),但每次执行时都会重新规划它。自定义计划至少生成 5 次。之后,如果成本低于迄今为止生成的自定义计划的平均成本,则计划者会考虑使用 通用计划(即参数值独立)。
请注意,计划的成本是对计划者的估计,而不是实际的 I/O 操作或 CPU 周期。
所以,问题可能会发生,但你需要一些坏运气。
2。 您建议的方法不起作用,因为它不会改变函数的行为。
一般来说,PostgreSQL 不使用参数并不是那么难看(就像 Oracle 一样),因为 PostgreSQL 没有用于计划的共享缓存。准备好的计划存储在每个后端的内存中,因此重新计划不会影响其他会话。
但据我所知,目前没有办法强制规划器使用自定义计划(除了在 5 次执行后重新连接......)。
【讨论】:
感谢您的回答。关于第一点我不太明白。这是否意味着当这种类型的查询被包装到函数中时,所描述的问题对于 PostgreSQL 来说通常不是问题? 这真的取决于运气。假设一个查询有一个参数,并且非常需要值 A 和 B 的不同计划。例如,A 要求顺序扫描(高成本),而 B 受益于索引扫描(低成本)。通用计划使用顺序扫描。您执行 F(A) 5 次,计划者决定可以切换到通用计划。现在你有一个问题:F(B) 不会使用索引扫描。但是如果您调用 F(A)、F(B)、F(A)、F(B) 等,平均定制成本将低于通用计划的成本,您是安全的。 好的,但我认为我们总是应该考虑最坏的情况,不是吗?看起来在最坏的情况下它仍然会是问题。我是否正确理解,对于一个函数,无论使用准备好的语句,都会计算计划? 是的,PL/pgSQL 函数隐式使用准备好的语句来执行 SQL 查询。 我不确定它何时在 postgres 中可用,但您可以强制规划器在第 5 次执行后继续使用自定义计划。例如,对于特定事务:'set local plan_cache_mode = force_custom_plan' 将执行此操作 (postgresql.org/docs/14/…)以上是关于PostgreSQL 函数内部的智能逻辑查询性能的主要内容,如果未能解决你的问题,请参考以下文章