我正在尝试优化PostgreSQL 9.1.2中的复杂查询,它调用了一些函数。这些函数标记为STABLE或IMMUTABLE,并在查询中使用相同的参数多次调用。我假设PostgreSQL足够智能,只能为每组输入调用一次 - 毕竟,这是STABLE和IMMUTABLE的重点,不是吗?但似乎多次调用这些函数。我写了一个简单的函数来测试它,这证实了它:
CREATE OR REPLACE FUNCTION test_multi_calls1(one integer)
RETURNS integer
AS $BODY$
BEGIN
RAISE NOTICE 'Called with %', one;
RETURN one;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE;
WITH data AS
(
SELECT 10 AS num
UNION ALL SELECT 10
UNION ALL SELECT 20
)
SELECT test_multi_calls1(num)
FROM data;
输出:
NOTICE: Called with 10
NOTICE: Called with 10
NOTICE: Called with 20
为什么会发生这种情况?如何让它只执行一次该功能?
答案 0 :(得分:24)
以下测试代码扩展内容丰富:
CREATE OR REPLACE FUNCTION test_multi_calls1(one integer)
RETURNS integer
AS $BODY$
BEGIN
RAISE NOTICE 'Immutable called with %', one;
RETURN one;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE;
CREATE OR REPLACE FUNCTION test_multi_calls2(one integer)
RETURNS integer
AS $BODY$
BEGIN
RAISE NOTICE 'Volatile called with %', one;
RETURN one;
END;
$BODY$ LANGUAGE plpgsql VOLATILE;
WITH data AS
(
SELECT 10 AS num
UNION ALL SELECT 10
UNION ALL SELECT 20
)
SELECT test_multi_calls1(num)
FROM data
where test_multi_calls2(40) = 40
and test_multi_calls1(30) = 30
输出:
NOTICE: Immutable called with 30
NOTICE: Volatile called with 40
NOTICE: Immutable called with 10
NOTICE: Volatile called with 40
NOTICE: Immutable called with 10
NOTICE: Volatile called with 40
NOTICE: Immutable called with 20
在这里我们可以看到,在select-list中,不可变函数被多次调用,在where子句中被调用一次,而volatile被调用了三次。
重要的是,PostgreSQL只会使用相同的数据调用一次STABLE
或IMMUTABLE
函数 - 您的示例清楚地表明情况并非如此 - 它就是可以只调用一次。或者它可能会在它需要调用易失性版本50次时调用它两次,依此类推。
有不同的方式可以利用不同的成本和收益来利用稳定性和不变性。为了提供这种保存,你建议它应该使用select-lists,它必须缓存结果,然后在返回缓存结果或在缓存上调用函数之前查找此缓存中的每个参数(或参数列表) -小姐。这比调用你的函数更昂贵,即使在缓存命中率很高的情况下(可能有0%的缓存命中率意味着这种“优化”做了额外的工作,完全没有收益)。它可能只存储最后一个参数和结果,但同样可能完全没用。
考虑到稳定和不可变函数通常是最轻的函数,尤其如此。
然而,使用where子句,test_multi_calls1
的不变性允许PostgreSQL从给定的SQL的简单含义中实际重构查询:
对于每一行计算test_multi_calls1(30),如果结果是 等于30继续处理有问题的行
完全针对不同的查询计划:
计算test_multi_calls1(30),如果它等于30则 继续查询否则返回零行结果集 任何进一步的计算
这是PostgreSQL对STABLE和IMMUTABLE的一种使用 - 不是对结果进行缓存,而是将查询重写为更高效但查看结果相同的查询。
另请注意,test_multi_calls1(30)在test_multi_calls2(40)之前调用,无论它们出现在where子句中的顺序如何。这意味着如果第一次调用导致没有返回任何行(将= 30
替换为= 31
进行测试),则根本不会调用volatile函数 - 无论哪个都在哪一侧都是and
。
这种特殊的改写取决于不变性或稳定性。使用where test_multi_calls1(30) != num
查询将重写不可变的但不仅仅是稳定的函数。使用where test_multi_calls1(num) != 30
它将不会发生(多次调用),尽管还有其他可能的优化:
仅包含STABLE和IMMUTABLE函数的表达式可用于索引扫描。包含VOLATILE函数的表达式不能。调用的数量可能会减少,也可能不会减少,但更重要的是,调用的结果将在查询的其余部分以更有效的方式使用(仅在大型表上真正重要,但随后它可以大量调用差)。
总之,不要考虑memoisation方面的波动率类别,而是考虑给PostgreSQL的查询计划器提供以逻辑等效(相同结果)但效率更高的方式重构整个查询的机会。
答案 1 :(得分:0)
根据documentation IMMUTABLE函数将返回给定相同参数的相同值。因为你正在提供动态参数(甚至不是同一个),优化器没有理由相信它会得到相同的结果,因此调用函数。更好的问题是:为什么你的查询如果不需要多次调用该函数?