【问题标题】:postgres functions: when does IMMUTABLE hurt performance?postgres 函数:IMMUTABLE 何时会损害性能?
【发布时间】:2013-08-15 18:11:29
【问题描述】:

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 Ringer,这已在pgsql-performance mailing list 上进行了讨论。亮点:

Tom Lane says

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

Pavel Stehule says

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

【问题讨论】:

  • 我的印象是 immutable 不能用于与随机或时区相关的任何东西,所以我猜它在运行时在内部将您的函数转换为 VOLATILE。
  • 嗯,文档警告“一个常见的错误是当函数的结果依赖于配置参数时将其标记为 IMMUTABLE。例如,操作时间戳的函数很可能具有依赖于 TimeZone 设置的结果. 为了安全起见,此类功能应改为 STABLE。但我不确定这会如何影响性能。
  • 请显示explain analyze生成的实际计划。您可以将它们粘贴到 explain.depesz.com 并在需要时将链接放在这里。 STRICT 通常会影响性能(它会阻止 SQL 函数的内联),但 添加 IMMUTABLE 不应该 AFAIK。
  • @CraigRinger 快速搜索档案让我相信 a)。我对神奇地发生不可变到易失性的事情是错误的,并且b)。我在考虑最近关于 STABLE 和 IMMUTABLE(不一定是 VOLATILE)的线程。这是一个链接,Tom Lane 直接与我之前的假设相矛盾:markmail.org/message/sekx7mjhq27vxu7d
  • @bma:我认为它是一个功能,你可以强制一个函数为IMMUTABLE。 Here is one application。当然,你需要知道他在搞什么鬼。

标签: performance postgresql user-defined-functions


【解决方案1】:

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

【讨论】:

  • 有趣的结果!知道为什么时区会产生这种影响吗?
猜你喜欢
  • 2011-10-13
  • 2014-06-17
  • 2018-05-29
  • 2010-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-10
  • 1970-01-01
相关资源
最近更新 更多