Postgres文档说
为了获得最佳优化结果,您应该使用对其有效的最严格的波动率类别来标记您的函数。
但是,我似乎有一个例子,情况并非如此,我想了解发生了什么。(背景:我正在运行 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 邮件列表中进行了讨论。强调:
[ 耸耸肩... ] 使用 IMMUTABLE 来谎报函数的可变性(在本例中为 date_trunc)是个坏主意。这可能会导致错误的答案,更不用说性能问题了。在这种特殊情况下,我认为性能问题来自于抑制了内联函数体的选项......但你应该更担心在其他情况下你是否没有得到完全虚假的答案。
如果我理解,使用的 IMMUTABLE 标志会禁用内联。你看到的是 SQL eval 溢出。我的规则是——尽可能不要在 SQL 函数中使用标志。