为什么PostgreSQL多次调用我的STABLE/IMMUTABLE函数?

EMP*_*EMP 16 postgresql performance function immutability

我正在尝试优化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;
Run Code Online (Sandbox Code Playgroud)

输出:

NOTICE:  Called with 10
NOTICE:  Called with 10
NOTICE:  Called with 20
Run Code Online (Sandbox Code Playgroud)

为什么会发生这种情况?如何让它只执行一次该功能?

Jon*_*nna 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
Run Code Online (Sandbox Code Playgroud)

OUTPUT:

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
Run Code Online (Sandbox Code Playgroud)

在这里我们可以看到,在select-list中,不可变函数被多次调用,在where子句中被调用一次,而volatile被称为三次.

最重要的事情不是PostgreSQL只是调用STABLEIMMUTABLE使用相同的数据函数一次-你的例子清楚地表明,这是不是这样的-那就是它可以调用它只有一次.或者,当它需要调用易失性版本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测试),那么挥发性功能将无法在全称为-再不管哪个是对的哪一侧and.

这种特殊的重写取决于不变性或稳定性.随着where test_multi_calls1(30) != num查询重写将发生不可变但不仅仅是稳定的函数.有了where test_multi_calls1(num) != 30它不会在所有的(多次调用)发生,虽然也有其他的优化可能:

仅包含STABLE和IMMUTABLE函数的表达式可用于索引扫描.包含VOLATILE函数的表达式不能.调用的数量可能会减少,也可能不会减少,但更重要的是,调用的结果将在查询的其余部分中以更有效的方式使用(仅对大型表格非常重要,但它可以大量调整区别).

总而言之,不要考虑memoisation方面的波动率类别,而是考虑给PostgreSQL的查询计划器提供以逻辑等效(相同结果)但更有效的方式重构整个查询的机会.