PostgreSQL: использование текстового параметра во вложенном CASE WHEN значительно замедляет запрос - PullRequest
0 голосов
/ 21 декабря 2018

Используя PostgreSQL 11.1, у меня есть функция с параметром типа text.Он интенсивно используется в CASE WHEN структурах, часто вложенных.

Недавно я столкнулся с очень странным явлением: скажем, в моей функции есть что-то вроде CASE WHEN $1 = 'foo') THEN id ..., теперь я выполняю функцию со значением параметраfoo.Все работает, как и ожидалось, но очень медленно.

Если внутри функции я заменю $1 = 'foo' на 'foo' = 'foo', это должно иметь тот же эффект, что и передача значения foo для $1.И действительно, результат тот же.Это просто намного быстрее!

В моем реальном примере разница составляет от 400 миллисекунд до 25 секунд!

Я создал две функции (см. Ниже), которые напоминают явление.Код там повторяется, чтобы получить некоторое значение.На моей машине версия без параметра занимает 6 секунд, а версия с параметром - около 16 секунд.(Я завернул выполнение в оператор PLV8 DO, чтобы результат не раздувал клиента)

Итак, мои вопросы: как получилось?Почему сравнение значения параметра со строкой занимает значительно больше времени, чем сравнение двух строк?Я не могу этого понять.Второй вопрос: можно ли здесь что-то сделать, чтобы улучшить производительность?Мне нужен этот параметр.

Редактировать: результаты EXPLAIN ANALYZE

Добавление 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)

Приложение: полный код функций

Код в основном бессмысленный: он генерирует огромную серию и 10 раз получает значение с вложенным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) Функция с параметром out .Я заменил $1 на /*P*/'foo'/*P*/, чтобы вы могли увидеть, что я здесь сделал

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$

1 Ответ

0 голосов
/ 21 декабря 2018

Если вы используете жестко закодированную константу, все выражения могут быть оценены по плану.Поскольку планы запросов кэшируются в функциях PL / pgSQL, это происходит только один раз.

Если вы используете параметр, выражения должны вычисляться во время выполнения каждый раз, когда вызывается функция.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...