postgres函数:什么时候IMUTABLE会影响性能



Postgres文档称

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

然而,我似乎有一个例子,但事实并非如此,我想了解发生了什么。(背景:我正在运行postgres9.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是否会花时间缓存结果?

(我正在运行postgres9.2。)

谢谢!

更新

感谢Craig Ringer,pgsql性能邮件列表中已经讨论过这一点。亮点:

Tom Lane说

[耸耸肩…]使用IMMUTABLE对函数的可变性撒谎(在本例中为date_trunc)是个坏主意。这很可能会导致错误答案,更不用说性能问题了。在这种特殊情况下,我假设性能问题来自于抑制了选项内联函数体。。。但你应该更担心在其他情况下,你是否没有得到完全虚假的答案。

Pavel Stehule说

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

问题是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

最新更新