1

使用 PostgreSQL 11.1,我有一个参数类型为 的函数text。它在CASE WHEN结构中大量使用,通常是嵌套的。

最近,我遇到了一个很奇怪的现象:假设在我的函数中我有类似的东西CASE WHEN $1 = 'foo') THEN id ...,我现在执行带有参数值的函数foo。一切都按预期工作,但速度很慢。

如果在函数内部,我用它替换$1 = 'foo'它应该与传递值的'foo' = 'foo'效果相同。结果确实是一样的。它只是更快!foo$1

在我的真实示例中,差异是 400 毫秒到 25 秒!

我创建了两个类似于这种现象的函数(见下文)。那里的代码高度重复以获得一些意义。在我的机器上,不带参数的版本需要 6 秒,而带参数的版本大约需要 16 秒。(我已经将执行包装在 PLV8DO语句中,这样结果就不会使客户端膨胀)

所以,我的问题是:怎么会?为什么将参数值与字符串进行比较比比较两个字符串花费的时间要长得多?我无法理解。第二个问题:我可以在这里做点什么来提高性能吗?我需要那个参数。

编辑:解释分析的结果

在函数EXPLAIN ANALYZE调用之前给我这些结果:

无参数

Result  (cost=0.00..0.26 rows=1 width=32) (actual time=5429.874..5432.217 rows=1 loops=1)
Planning Time: 0.615 ms
Execution Time: 5435.469 ms

带参数

Result  (cost=0.00..0.26 rows=1 width=32) (actual time=15585.637..15588.569 rows=1 loops=1)
Planning Time: 0.213 ms
Execution Time: 15591.640 ms

编辑 2:自动记录的结果

无参数

Aggregate  (cost=47.52..47.53 rows=1 width=32) (actual time=6248.177..6248.178 rows=1 loops=1)
          CTE myData
            ->  ProjectSet  (cost=0.00..5.02 rows=1000 width=4) (actual time=0.003..689.085 rows=10000000 loops=1)
                  ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.000..0.001 rows=1 loops=1)
          CTE nestedCases
            ->  CTE Scan on "myData"  (cost=0.00..20.00 rows=1000 width=40) (actual time=0.004..2692.660 rows=10000000 loops=1)
          ->  CTE Scan on "nestedCases"  (cost=0.00..20.00 rows=1000 width=4) (actual time=0.005..5434.799 rows=10000000 loops=1)

带参数

Aggregate  (cost=197.52..197.53 rows=1 width=32) (actual time=16568.033..16568.033 rows=1 loops=1)
          CTE myData
            ->  ProjectSet  (cost=0.00..5.02 rows=1000 width=4) (actual time=0.002..728.866 rows=10000000 loops=1)
                  ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.000..0.001 rows=1 loops=1)
          CTE nestedCases
            ->  CTE Scan on "myData"  (cost=0.00..170.00 rows=1000 width=40) (actual time=0.010..12851.991 rows=10000000 loops=1)
          ->  CTE Scan on "nestedCases"  (cost=0.00..20.00 rows=1000 width=4) (actual time=0.012..15686.157 rows=10000000 loops=1)

附录:完整的函数代码

该代码基本上是无稽之谈:它生成一个巨大的系列并使用嵌套的CASE WHEN.

A) 带参数的函数

    CREATE OR REPLACE FUNCTION public.function_with_param(role text)
 RETURNS integer[]
 LANGUAGE sql
 STABLE
AS $function$
 WITH "myData" AS (
   SELECT generate_series(1,10000000) AS id
),

"nestedCases" AS (
 SELECT 
 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
      CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
           WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id2,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id3,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id4,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id5,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id6,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
      CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
           WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id7,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id8,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id9,

 CASE WHEN ($1 = 'bar') THEN 0
 WHEN ($1 = 'foo') THEN 
       CASE WHEN ($1 = 'huhu') AND id = 1 THEN id + 452
            WHEN ($1 = 'foo') THEN id
       END   
 END  
 AS id10

FROM "myData"
)
SELECT array_agg(id) FROM "nestedCases"
$function$

B)没有参数的函数。我已经替换$1/*P*/'foo'/*P*/just 这样你就可以看到我在这里做了什么

CREATE OR REPLACE FUNCTION public.function_without_param()
 RETURNS integer[]
 LANGUAGE sql
 STABLE
AS $function$
 WITH "myData" AS (
   SELECT generate_series(1,10000000) AS id
),
"nestedCases" AS (
 SELECT 
 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
      CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
           WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id2,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id3,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id4,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id5,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id6,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
      CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
           WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id7,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id8,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id9,

 CASE WHEN (/*P*/'foo'/*P*/ = 'bar') THEN 0
 WHEN (/*P*/'foo'/*P*/ = 'foo') THEN 
       CASE WHEN (/*P*/'foo'/*P*/ = 'huhu') AND id = 1 THEN id + 452
            WHEN (/*P*/'foo'/*P*/ = 'foo') THEN id
       END   
 END  
 AS id10

FROM "myData"
)
SELECT array_agg(id) FROM "nestedCases"
$function$
4

1 回答 1

1

如果您使用硬编码常量,则所有表达式都可以在计划时间内进行评估。由于查询计划缓存在 PL/pgSQL 函数中,因此只会发生一次。

如果使用参数,则每次调用函数时都必须在运行时计算表达式。

于 2018-12-21T10:11:23.887 回答