Postgresql 嵌套plpgsql函数中的计算比直接查询中的计算慢?

Postgresql 嵌套plpgsql函数中的计算比直接查询中的计算慢?,postgresql,plpgsql,sql-execution-plan,Postgresql,Plpgsql,Sql Execution Plan,我有一个包含一些文本和一些数字列的表格,例如: dimension_1, dimension_2, counter_1, counter_2 而不是执行查询 SELECT dimension_1, dimension_2, (counter_1, NULLIF(counter_2, 0)) as kpi from table order by kpi desc nulls last; 我想创建一个函数并执行以下操作: SELECT dimension_1, dimension_2, fun

我有一个包含一些
文本
和一些
数字
列的表格,例如:

dimension_1, dimension_2, counter_1, counter_2
而不是执行查询

SELECT dimension_1, dimension_2, (counter_1, NULLIF(counter_2, 0)) as kpi 
from table order by kpi desc nulls last;
我想创建一个函数并执行以下操作:

SELECT dimension_1, dimension_2, func(counter_1, counter_2) as kpi
from table order by kpi desc nulls last;
我在Postgres中使用了以下实现:

CREATE FUNCTION kpi_latency_ext_msec(val1 numeric, val2 numeric)     
RETURNS numeric AS $func$
BEGIN

RETURN ($1 / NULLIF($2, 0::numeric));             

END; $func$
LANGUAGE PLPGSQL SECURITY DEFINER IMMUTABLE; 
并获得期望的结果,但性能较慢

解释分析
我得到:

第一次查询(带func):

Sort  (cost=800.85..806.75 rows=2358 width=26) (actual  time=5.534..5.710 rows=2358 loops=1)
Sort Key: (kpi_latency_ext_msec(external_tcp_handshake_latency_sum, external_tcp_handshake_latency_samples))
Sort Method: quicksort  Memory: 281kB
 ->  Seq Scan on counters_by_cgi_rat  (cost=0.00..668.76 rows=2358 width=26) (actual time=0.142..4.233 rows=2358 loops=1)
Filter: (("timestamp" >= '2018-05-10 00:00:00'::timestamp without time zone) AND ("timestamp" < '2018-05-13 00:00:00'::timestamp without time zone) AND (granularity = '1 day'::interval))
Planning time: 0.221 ms
Execution time: 5.881 ms
Sort  (cost=223.14..229.04 rows=2358 width=26) (actual time=1.933..2.114 rows=2358 loops=1)

Sort Key: ((external_tcp_handshake_latency_sum / NULLIF(external_tcp_handshake_latency_samples, 0::numeric)))
Sort Method: quicksort  Memory: 281kB
->  Seq Scan on counters_by_cgi_rat  (cost=0.00..91.06 rows=2358 width=26) (actual time=0.010..1.190 rows=2358 loops=1)
Filter: (("timestamp" >= '2018-05-10 00:00:00'::timestamp without time zone) AND ("timestamp" < '2018-05-13 00:00:00'::timestamp without time zone) AND (granularity = '1 day'::interval))
Planning time: 0.139 ms
Execution time: 2.279 ms
Seq Scan on table (cost=0.00..91.06 rows=2358 width=26) (actual time=0.016..1.223 rows=2358 loops=1)
Seq Scan on table (cost=0.00..668.76 rows=2358 width=26) (actual time=0.123..3.518 rows=2358 loops=1)
与func:

Sort  (cost=800.85..806.75 rows=2358 width=26) (actual  time=5.534..5.710 rows=2358 loops=1)
Sort Key: (kpi_latency_ext_msec(external_tcp_handshake_latency_sum, external_tcp_handshake_latency_samples))
Sort Method: quicksort  Memory: 281kB
 ->  Seq Scan on counters_by_cgi_rat  (cost=0.00..668.76 rows=2358 width=26) (actual time=0.142..4.233 rows=2358 loops=1)
Filter: (("timestamp" >= '2018-05-10 00:00:00'::timestamp without time zone) AND ("timestamp" < '2018-05-13 00:00:00'::timestamp without time zone) AND (granularity = '1 day'::interval))
Planning time: 0.221 ms
Execution time: 5.881 ms
Sort  (cost=223.14..229.04 rows=2358 width=26) (actual time=1.933..2.114 rows=2358 loops=1)

Sort Key: ((external_tcp_handshake_latency_sum / NULLIF(external_tcp_handshake_latency_samples, 0::numeric)))
Sort Method: quicksort  Memory: 281kB
->  Seq Scan on counters_by_cgi_rat  (cost=0.00..91.06 rows=2358 width=26) (actual time=0.010..1.190 rows=2358 loops=1)
Filter: (("timestamp" >= '2018-05-10 00:00:00'::timestamp without time zone) AND ("timestamp" < '2018-05-13 00:00:00'::timestamp without time zone) AND (granularity = '1 day'::interval))
Planning time: 0.139 ms
Execution time: 2.279 ms
Seq Scan on table (cost=0.00..91.06 rows=2358 width=26) (actual time=0.016..1.223 rows=2358 loops=1)
Seq Scan on table (cost=0.00..668.76 rows=2358 width=26) (actual time=0.123..3.518 rows=2358 loops=1)
功能的结果无安全定义

Seq Scan on counters_by_cgi_rat  (cost=0.00..668.76 rows=2358 width=26) 
                                  (actual time=0.035..3.718 rows=2358 loops=1)
Filter: (("timestamp" >= '2018-05-10 00:00:00'::timestamp without time zone) 
        AND ("timestamp" < '2018-05-13 00:00:00'::timestamp without time zone) 
        AND (granularity = '1 day'::interval))
  Planning time: 0.086 ms
  Execution time: 3.923 ms

使用上述功能获得的最佳结果(甚至比普通查询更快)

毒药镖是
安全定义者
。声明的函数
安全定义器
不能内联-如果我没有弄错的话,可以强制执行上下文切换。这会使它们的价格大大提高。在本例中,确实不需要
安全定义程序
。简单计算不需要不同的权限。(可能您的实际用例不同。)

而且也不需要PL/pgSQL只有SQL函数可以内联-如果满足一些附加的先决条件

由于所有使用的函数都是不可变的
,因此应该声明函数不可变
。(默认函数VOLATILE为
VOLATILE
)您已经相应地更新了问题。这允许表达式索引,并有助于防止在某些情况下重复计算。但是它从不帮助函数内联。相反:它强加了更多的先决条件(在本例中满足这些先决条件)。引用(撰写本文时的2016年最新更新):

如果函数声明为不可变,则表达式不能 调用任何不可变的函数或运算符

引述:

这里的基本要点是,标记为volatile的函数可以 扩展到包含的函数,即使它们是不可变的;但是 另一方面,表示潜在的语义变化,因此 计划者不会这么做的

解决方案 尝试不使用
安全定义程序

CREATE FUNCTION kpi_latency_ext_msec(val1 numeric, val2 numeric)     
  RETURNS numeric AS
$func$
BEGIN
   RETURN $1 / NULLIF($2, numeric '0');
END
$func$  LANGUAGE plpgsql IMMUTABLE; 
应该已经快多了

或者从根本上简化为SQL函数:

CREATE FUNCTION f_div0_sql_nullif(val1 numeric, val2 numeric)     
  RETURNS numeric LANGUAGE sql IMMUTABLE AS
$$SELECT $1 / NULLIF($2, numeric '0')$$;
更快了吗

相关的:

基准 起初我使用了
IF
CASE
表达式,但是在我运行了大量测试之后,显示
NULLIF
稍微快一点。因此,我相应地简化为原始的
NULLIF
变体

主要的要点仍然是没有
安全定义符
SQL
不可变

DBFIDLE-第10页

dbfiddle-pg 9.4

我可能会将其简化为:
选择$1/nullif($2,0)
-在这种情况下,
案例会更快吗?@a_horse_没有名字:我运行了一些测试,发现你是对的:
nullif
似乎稍微快一点。添加了上述结果。使用nullif并将函数定义为稳定函数比普通函数具有更好的性能query@dimitrislepipas:
稳定的
不可变的
在您的情况下应该会导致此函数具有相同的性能。(我实际测试过。)但是
不可变
通常更可取,如果适用的话,因为它还有其他好处。它可以用于表达式索引等。