Вычисление во вложенной функции plpgsql медленнее, чем в запросе напрямую? - PullRequest
0 голосов
/ 14 мая 2018

У меня есть таблица с некоторыми text и некоторыми numeric столбцами, например:

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;

Я хочу создатьfunction and do:

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; 

и получил желаемый результат, но с более низкой производительностью.

От EXPLAIN ANALYZEЯ получаю:

1-й запрос (с функцией):

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

2-й запрос (без функции):

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

Выполнение запросов без ORDER BY:

Без функции:

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

РЕЗУЛЬТАТЫ для простого запроса

Seq Scan on counters_by_cgi_rat  (cost=0.00..91.06 rows=2358 width=26)    
                                 (actual time=0.017..1.175 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.105 ms
 Execution time: 1.356 ms

РЕЗУЛЬТАТЫ с языком SQL

 Seq Scan on counters_by_cgi_rat  (cost=0.00..91.06 rows=2358 width=26)          
                                  (actual time=0.011..1.123 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.180 ms
 Execution time: 1.294 ms

БЫСТРО достаточно с языком SQL

Конечно, это быстрее, чем с использованием языка plpgsql но немного медленнее, чем исходный запрос (после повторных запусков)

========= ОБНОВЛЕНИЕ =========

CREATE FUNCTION kpi_latency_ext_msec(val1 numeric, val2 numeric)
RETURNS numeric LANGUAGE sql STABLE AS
'SELECT $1 / NULLIF($2, 0)';

Наилучшие результаты, полученные с помощью вышеуказанной функции (даже быстрее, чем простой запрос)

1 Ответ

0 голосов
/ 14 мая 2018

Ядовитый дротик SECURITY DEFINER. Объявленные функции SECURITY DEFINER не могут быть встроенными - и принудительно переключать контекст, если я не ошибаюсь Это может сделать их значительно дороже . В этом примере SECURITY DEFINER не требуется. Вам не нужны разные привилегии для простого расчета. (Возможно, ваш фактический вариант использования отличается.)

И PL / pgSQL также не требуется. Только функции SQL могут быть встроены - если выполняются некоторые дополнительные предварительные условия.

Поскольку все используемые функции IMMUTABLE, вы должны объявить функцию IMMUTABLE. (Волатильность функции по умолчанию VOLATILE.) Вы уже обновили вопрос соответствующим образом. Это позволяет использовать индексы выражений и может помочь предотвратить повторную оценку в некоторых ситуациях. Но это никогда не помогает при встраивании функций. Au противоречит: это налагает больше предварительных условий (которые встречаются в этом случае). Цитирование Postgres Wiki по функции inlining (последнее обновление 2016 на момент написания):

если функция объявлена ​​IMMUTABLE, то выражение не должно вызвать любую неизменяемую функцию или оператор

Цитата Том Лейн на pgsql-performance :

Суть в том, что функция, помеченная как volatile, может быть расширен до встроенных функций, даже если они неизменны; но наоборот, представляет потенциальное семантическое изменение, поэтому планировщик этого не сделает.

Решение

Попробуйте без SECURITY DEFINER:

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')$$;

Еще быстрее?

Связанный:

Benchmark

Сначала я использовал выражения IF и CASE, но после комментария a_horse_with_no_name Я провел обширные тесты, показывающие, что NULLIF немного быстрее. Поэтому я упростил до оригинального NULLIF варианта соответственно.

Основными точками пока не являются SECURITY DEFINER, SQL и IMMUTABLE.

дБ <> скрипка здесь - стр. 10
дБ <> скрипка здесь - стр 9,4

...