Используя 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$