Postgres:多次调用 STABLE 函数
Postgres: STABLE function called multiple times on constant
我遇到了 Postgresql(9.4 版)性能难题。我有一个函数 (prevd
) 声明为 STABLE
(见下文)。当我 运行 这个函数在 where
子句中的常量上时,它被多次调用 - 而不是一次。
如果我正确理解 postgres 文档,查询应该优化为只调用一次 prevd
。
A STABLE function cannot modify the database and is guaranteed to return the same results given the same arguments for all rows within a single statement
为什么在这种情况下不优化对 prevd
的调用?
我不希望对同一参数使用 prevd
的所有后续查询调用一次 prevd
(就像它是 IMMUTABLE 一样)。我希望 postgres 只需调用一次 prevd('2015-12-12')
即可为我的查询创建一个计划
请找到下面的代码:
架构
create table somedata(d date, number double precision);
create table dates(d date);
insert into dates
select generate_series::date
from generate_series('2015-01-01'::date, '2015-12-31'::date, '1 day');
insert into somedata
select '2015-01-01'::date + (random() * 365 + 1)::integer, random()
from generate_series(1, 100000);
create or replace function prevd(date_ date)
returns date
language sql
stable
as $$
select max(d) from dates where d < date_;
$$
慢查询
select avg(number) from somedata where d=prevd('2015-12-12');
上面查询的查询计划很差
Aggregate (cost=28092.74..28092.75 rows=1 width=8) (actual time=3532.638..3532.638 rows=1 loops=1)
Output: avg(number)
-> Seq Scan on public.somedata (cost=0.00..28091.43 rows=525 width=8) (actual time=10.210..3532.576 rows=282 loops=1)
Output: d, number
Filter: (somedata.d = prevd('2015-12-12'::date))
Rows Removed by Filter: 99718
Planning time: 1.144 ms
Execution time: 3532.688 ms
(8 rows)
性能
上面的查询,在我的机器上 运行s 大约 3.5s。将 prevd
更改为 IMMUTABLE 后,它正在更改为 0.035s。
我开始将其作为评论来写,但它有点长,所以我将其扩展为一个答案。
如 this previous answer 中所述,Postgres 不承诺 总是 基于 STABLE
或 IMMUTABLE
注释进行优化,只是它可以有时这样做。它通过利用某些假设以不同方式规划查询来实现这一点。之前答案的这一部分直接类似于你的情况:
This particular sort of rewriting depends upon immutability or stability. With where test_multi_calls1(30) != num
query re-writing will happen for immutable
but not for merely stable
functions.
如果你把函数改成IMMUTABLE
,再看看查询计划,你会发现它做的重写真的很激进:
Seq Scan on public.somedata (cost=0.00..1791.00 rows=272 width=12) (actual time=0.036..14.549 rows=270 loops=1)
Output: d, number
Filter: (somedata.d = '2015-12-11'::date)
Buffers: shared read=541 written=14
Total runtime: 14.589 ms
它实际上运行在计划查询时使用函数,并在查询执行之前替换值 .使用 STABLE
函数,这种优化显然不合适 - 数据可能会在计划和执行查询之间发生变化。
评论中提到此查询会产生优化计划:
select avg(number) from somedata where d=(select prevd(date '2015-12-12'));
这很快,但请注意,该计划看起来与 IMMUTABLE
版本所做的完全不同:
Aggregate (cost=1791.69..1791.70 rows=1 width=8) (actual time=14.670..14.670 rows=1 loops=1)
Output: avg(number)
Buffers: shared read=541 written=21
InitPlan 1 (returns [=12=])
-> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Output: '2015-12-11'::date
-> Seq Scan on public.somedata (cost=0.00..1791.00 rows=273 width=8) (actual time=0.026..14.589 rows=270 loops=1)
Output: d, number
Filter: (somedata.d = [=12=])
Buffers: shared read=541 written=21
Total runtime: 14.707 ms
通过将其放入子查询,您将函数调用从 WHERE 子句移动到 SELECT 子句。更重要的是,子查询可以总是执行一次并被其余查询使用;所以这个函数在计划的一个单独节点中是 运行 一次。
为了证实这一点,我们可以将 SQL 完全从函数中取出:
select avg(number) from somedata where d=(select max(d) from dates where d < '2015-12-12');
这给出了一个相当长的计划,但性能非常相似:
Aggregate (cost=1799.12..1799.13 rows=1 width=8) (actual time=14.174..14.174 rows=1 loops=1)
Output: avg(somedata.number)
Buffers: shared read=543 written=19
InitPlan 1 (returns [=14=])
-> Aggregate (cost=7.43..7.44 rows=1 width=4) (actual time=0.150..0.150 rows=1 loops=1)
Output: max(dates.d)
Buffers: shared read=2
-> Seq Scan on public.dates (cost=0.00..6.56 rows=347 width=4) (actual time=0.015..0.103 rows=345 loops=1)
Output: dates.d
Filter: (dates.d < '2015-12-12'::date)
Buffers: shared read=2
-> Seq Scan on public.somedata (cost=0.00..1791.00 rows=273 width=8) (actual time=0.190..14.098 rows=270 loops=1)
Output: somedata.d, somedata.number
Filter: (somedata.d = [=14=])
Buffers: shared read=543 written=19
Total runtime: 14.232 ms
需要注意的重要一点是内部聚合(max(d)
)在与主 Seq Scan(检查 where
子句)不同的节点上执行一次。在这个位置,即使是VOLATILE
函数也可以用同样的方式优化。
简而言之,虽然 你 知道你生成的查询可以通过只执行一次函数来优化,但它不匹配 Postgres 查询的任何模式planner 知道如何重写,所以它使用了一个朴素的计划,运行s 函数多次。
[注:所有测试都是在Postgres 9.1上进行的,因为正好我手上有这个。]
我遇到了 Postgresql(9.4 版)性能难题。我有一个函数 (prevd
) 声明为 STABLE
(见下文)。当我 运行 这个函数在 where
子句中的常量上时,它被多次调用 - 而不是一次。
如果我正确理解 postgres 文档,查询应该优化为只调用一次 prevd
。
A STABLE function cannot modify the database and is guaranteed to return the same results given the same arguments for all rows within a single statement
为什么在这种情况下不优化对 prevd
的调用?
我不希望对同一参数使用 prevd
的所有后续查询调用一次 prevd
(就像它是 IMMUTABLE 一样)。我希望 postgres 只需调用一次 prevd('2015-12-12')
请找到下面的代码:
架构
create table somedata(d date, number double precision);
create table dates(d date);
insert into dates
select generate_series::date
from generate_series('2015-01-01'::date, '2015-12-31'::date, '1 day');
insert into somedata
select '2015-01-01'::date + (random() * 365 + 1)::integer, random()
from generate_series(1, 100000);
create or replace function prevd(date_ date)
returns date
language sql
stable
as $$
select max(d) from dates where d < date_;
$$
慢查询
select avg(number) from somedata where d=prevd('2015-12-12');
上面查询的查询计划很差
Aggregate (cost=28092.74..28092.75 rows=1 width=8) (actual time=3532.638..3532.638 rows=1 loops=1)
Output: avg(number)
-> Seq Scan on public.somedata (cost=0.00..28091.43 rows=525 width=8) (actual time=10.210..3532.576 rows=282 loops=1)
Output: d, number
Filter: (somedata.d = prevd('2015-12-12'::date))
Rows Removed by Filter: 99718
Planning time: 1.144 ms
Execution time: 3532.688 ms
(8 rows)
性能
上面的查询,在我的机器上 运行s 大约 3.5s。将 prevd
更改为 IMMUTABLE 后,它正在更改为 0.035s。
我开始将其作为评论来写,但它有点长,所以我将其扩展为一个答案。
如 this previous answer 中所述,Postgres 不承诺 总是 基于 STABLE
或 IMMUTABLE
注释进行优化,只是它可以有时这样做。它通过利用某些假设以不同方式规划查询来实现这一点。之前答案的这一部分直接类似于你的情况:
This particular sort of rewriting depends upon immutability or stability. With
where test_multi_calls1(30) != num
query re-writing will happen forimmutable
but not for merelystable
functions.
如果你把函数改成IMMUTABLE
,再看看查询计划,你会发现它做的重写真的很激进:
Seq Scan on public.somedata (cost=0.00..1791.00 rows=272 width=12) (actual time=0.036..14.549 rows=270 loops=1)
Output: d, number
Filter: (somedata.d = '2015-12-11'::date)
Buffers: shared read=541 written=14
Total runtime: 14.589 ms
它实际上运行在计划查询时使用函数,并在查询执行之前替换值 .使用 STABLE
函数,这种优化显然不合适 - 数据可能会在计划和执行查询之间发生变化。
评论中提到此查询会产生优化计划:
select avg(number) from somedata where d=(select prevd(date '2015-12-12'));
这很快,但请注意,该计划看起来与 IMMUTABLE
版本所做的完全不同:
Aggregate (cost=1791.69..1791.70 rows=1 width=8) (actual time=14.670..14.670 rows=1 loops=1)
Output: avg(number)
Buffers: shared read=541 written=21
InitPlan 1 (returns [=12=])
-> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Output: '2015-12-11'::date
-> Seq Scan on public.somedata (cost=0.00..1791.00 rows=273 width=8) (actual time=0.026..14.589 rows=270 loops=1)
Output: d, number
Filter: (somedata.d = [=12=])
Buffers: shared read=541 written=21
Total runtime: 14.707 ms
通过将其放入子查询,您将函数调用从 WHERE 子句移动到 SELECT 子句。更重要的是,子查询可以总是执行一次并被其余查询使用;所以这个函数在计划的一个单独节点中是 运行 一次。
为了证实这一点,我们可以将 SQL 完全从函数中取出:
select avg(number) from somedata where d=(select max(d) from dates where d < '2015-12-12');
这给出了一个相当长的计划,但性能非常相似:
Aggregate (cost=1799.12..1799.13 rows=1 width=8) (actual time=14.174..14.174 rows=1 loops=1)
Output: avg(somedata.number)
Buffers: shared read=543 written=19
InitPlan 1 (returns [=14=])
-> Aggregate (cost=7.43..7.44 rows=1 width=4) (actual time=0.150..0.150 rows=1 loops=1)
Output: max(dates.d)
Buffers: shared read=2
-> Seq Scan on public.dates (cost=0.00..6.56 rows=347 width=4) (actual time=0.015..0.103 rows=345 loops=1)
Output: dates.d
Filter: (dates.d < '2015-12-12'::date)
Buffers: shared read=2
-> Seq Scan on public.somedata (cost=0.00..1791.00 rows=273 width=8) (actual time=0.190..14.098 rows=270 loops=1)
Output: somedata.d, somedata.number
Filter: (somedata.d = [=14=])
Buffers: shared read=543 written=19
Total runtime: 14.232 ms
需要注意的重要一点是内部聚合(max(d)
)在与主 Seq Scan(检查 where
子句)不同的节点上执行一次。在这个位置,即使是VOLATILE
函数也可以用同样的方式优化。
简而言之,虽然 你 知道你生成的查询可以通过只执行一次函数来优化,但它不匹配 Postgres 查询的任何模式planner 知道如何重写,所以它使用了一个朴素的计划,运行s 函数多次。
[注:所有测试都是在Postgres 9.1上进行的,因为正好我手上有这个。]