考虑以下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$
到目前为止,我误解了几点:
在功能包装的情况下是否仍会出现此问题? (我已经尝试查看函数调用的执行计划,但Postgres没有向我显示内部函数调用的详细信息,即使使用SET auto_explain.log_nested_statements = ON
)。
让我们说我正在使用遗留项目,并且无法更改函数本身,只能更改java执行代码。在这里避免使用预处理语并每次使用动态查询会更好吗? (假设执行时间很长,最多几秒钟)。说这个,可能是丑陋方法:
getSession().doWork(connection -> {
ResultSet rs = connection.createStatement().executeQuery("select * from func("+id1+","+id2+")");
...
})
答案 0 :(得分:2)
1。 这取决于。
当不使用预准备语句时,PostgreSQL每次使用参数值计划一次查询。它被称为自定义计划。
使用预先准备好的陈述(你是对的,PL / pgSQL函数确实使用预备语句),它更复杂。 PostgreSQL准备语句(解析其文本并存储解析树),但每次执行时都重新计划。自定义计划至少生成5次。之后,如果计划程序的成本低于目前生成的自定义计划的平均成本,则计划程序会考虑使用通用计划(即参数值独立)。
注意,计划的成本是对计划者的估计,而不是实际的I / O操作或CPU周期。
所以,问题可以发生,但你需要运气不好。
2。 您建议的方法不起作用,因为它不会改变函数的行为。
一般来说,PostgreSQL不要使用参数(例如Oracle)就不那么难看了,因为PostgreSQL没有为计划提供共享缓存。准备好的计划存储在每个后端的内存中,因此重新规划不会影响其他会话。
但据我所知,目前没有办法强迫规划人员使用自定义计划(除了5次执行后重新连接......)。