postgres功能:IMMUTABLE什么时候会伤害性能?

时间:2013-08-13 23:10:47

标签: performance postgresql user-defined-functions

Postgres docs

  

为了获得最佳的优化结果,您应该使用对他们有效的最严格的波动率类别标记您的函数。

但是,我似乎有一个例子,并非如此,我想了解发生了什么。 (背景:我正在运行postgres 9.2)

我经常需要将表示为整数秒数的时间转换为日期。我已经编写了一个函数来执行此操作:

CREATE OR REPLACE FUNCTION 
  to_datestamp(time_int double precision) RETURNS date AS $$
  SELECT date_trunc('day', to_timestamp($1))::date;
$$ LANGUAGE SQL;

让我们将性能与其他相同的函数进行比较,将波动率设置为IMMUTABLE和STABLE:

CREATE OR REPLACE FUNCTION 
  to_datestamp_immutable(time_int double precision) RETURNS date AS $$
  SELECT date_trunc('day', to_timestamp($1))::date;
$$ LANGUAGE SQL IMMUTABLE;
CREATE OR REPLACE FUNCTION 
  to_datestamp_stable(time_int double precision) RETURNS date AS $$
  SELECT date_trunc('day', to_timestamp($1))::date;
$$ LANGUAGE SQL STABLE;

为了测试这一点,我将创建一个10 ^ 6个随机整数的表,对应于2010-01-01和2015-01-01之间的时间

CREATE TEMPORARY TABLE random_times AS
  SELECT 1262304000 + round(random() * 157766400) AS time_int 
  FROM generate_series(1, 1000000) x;

最后,我会在这张桌子上调用两个函数;在我的特定盒子上,原始需要约6秒,不可变版本需要~33秒,而稳定版本需要约6秒。

EXPLAIN ANALYZE SELECT to_datestamp(time_int) FROM random_times;

Seq Scan on random_times  (cost=0.00..20996.62 rows=946950 width=8) 
  (actual time=0.150..5493.722 rows=1000000 loops=1)
Total runtime: 6258.827 ms


EXPLAIN ANALYZE SELECT to_datestamp_immutable(time_int) FROM random_times;

Seq Scan on random_times  (cost=0.00..250632.00 rows=946950 width=8) 
  (actual time=0.211..32209.964 rows=1000000 loops=1)
Total runtime: 33060.918 ms


EXPLAIN ANALYZE SELECT to_datestamp_stable(time_int) FROM random_times;
Seq Scan on random_times  (cost=0.00..20996.62 rows=946950 width=8)
  (actual time=0.086..5295.608 rows=1000000 loops=1)
Total runtime: 6063.498 ms

这里发生了什么?例如,postgres花时间缓存结果,因为传递给函数的参数不太可能重复,因此实际上没有用处?

(我正在运行postgres 9.2。)

谢谢!

更新

感谢Craig Ringerpgsql-performance mailing list已对此进行了讨论。亮点:

Tom Lane says

  

[耸耸肩......]使用IMMUTABLE来解释函数的可变性   (在这种情况下,date_trunc)是个坏主意。它可能导致错误   答案,不要介意性能问题。在这个特殊情况下,我   想象性能问题来自抑制选项   内联函数体......但你应该更担心   在其他情况下,你是否没有得到平坦的虚假答案。

Pavel Stehule says

  

如果我理解,使用过的IMMUTABLE标志会禁用内联。你看到的是,是   SQL eval溢出。我的规则是 - 尽可能不在SQL函数中使用标志。

1 个答案:

答案 0 :(得分:2)

问题是to_timestamp会返回带时区的时间戳。如果to_timestamp函数被替换为没有时区的“手动”计算,则性能没有差异

create or replace function to_datestamp_stable(
    time_int double precision
) returns date as $$
  select date_trunc('day', timestamp 'epoch' + $1 * interval '1 second')::date;
$$ language sql stable;

explain analyze
select to_datestamp_stable(a)
from generate_series(1, 1000000) s (a);
                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series s  (cost=0.00..22.50 rows=1000 width=4) (actual time=96.962..433.562 rows=1000000 loops=1)
 Total runtime: 459.531 ms

create or replace function to_datestamp_immutable(
    time_int double precision
) returns date as $$
  select date_trunc('day', timestamp 'epoch' + $1 * interval '1 second')::date;
$$ language sql immutable;

explain analyze
select to_datestamp_immutable(a)
from generate_series(1, 1000000) s (a);
                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series s  (cost=0.00..22.50 rows=1000 width=4) (actual time=94.188..433.492 rows=1000000 loops=1)
 Total runtime: 459.434 ms

使用to_timestamp

的相同功能
create or replace function to_datestamp_stable(
    time_int double precision
) returns date as $$
  select date_trunc('day', to_timestamp($1))::date;
$$ language sql stable;

explain analyze
select to_datestamp_stable(a)
from generate_series(1, 1000000) s (a);
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series s  (cost=0.00..20.00 rows=1000 width=4) (actual time=91.924..3059.570 rows=1000000 loops=1)
 Total runtime: 3103.655 ms

create or replace function to_datestamp_immutable(
    time_int double precision
) returns date as $$
  select date_trunc('day', to_timestamp($1))::date;
$$ language sql immutable;

explain analyze
select to_datestamp_immutable(a)
from generate_series(1, 1000000) s (a);
                                                           QUERY PLAN                                                           
--------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series s  (cost=0.00..262.50 rows=1000 width=4) (actual time=92.639..20083.920 rows=1000000 loops=1)
 Total runtime: 20149.311 ms