Поведение встроенных функций PostgreSQL - PullRequest
0 голосов
/ 29 октября 2018

Интересно, кто-нибудь может подтвердить ожидаемое поведение Inlining PostgreSQL?

В мире Microsoft SQL любая функция, которая определена как встроенная, будет использоваться за одну итерацию на нескольких строках (Тело функции по существу вставляется в оператор вызывающей стороны, что делает его основанным на множестве [Один вызов ] вместо проверки на входную строку данных [Много вызовов]).

Моя команда и я боролись за то, чтобы доказать это без Profiler, такого как в MSSQL, но в итоге мы смогли это доказать и обнаружили, что число итераций в нашей функции, которую мы считаем встроенной, прямо пропорционально количеству строк над которым он работает.

Мы сделали это, введя преднамеренное ожидание в функцию (pg_sleep), где мы можем видеть, что ожидание в N секунд приводит к общему времени выполнения строк * N, т. Е. На входе из 6 строк ожидание 1 секунды 6 секунд, ожидание 2 - 12 и т. Д.

Итак, наши вопросы:

  1. Является ли встраивание в PostgreSQL тем, что мы думаем (эквивалентно встроенной функции MSSQL [Type = 'IF'])?
  2. Существует ли инструмент профилирования, который может показать это наглядно, как это умеет Profiler в MSSQL?
  3. Существуют ли какие-либо маркеры метаданных, на которые мы можем посмотреть, чтобы подтвердить / опровергнуть, что наша функция действительно встроена?

1 Ответ

0 голосов
/ 29 октября 2018

Термин «встраивание» имеет другое значение в Postgres. Обычно это относится к language sql функциям, которые полностью заменяются вложенным запросом при использовании внутри другого запроса, например, эта функция

create or replace function customers_in_zip(p_zip_code varchar(5))
  returns setof customers
as
$$
  select *
  from customers
  where zip_code = p_zip_code;
$$
language sql;

используется так:

select *
from orders o 
  join customers_in_zip('42') c on o.customer_id = c.id;

будет расширен оптимизатором до:

select *
from orders o 
  join customers c on o.customer_id = c.id and c.zip_code = '42';

Этот тип встраивания можно увидеть при генерации плана выполнения с использованием explain (analyze). Для этого функция должна быть помечена как immutable или stable

например. если функция может быть «встроенной», план выглядит примерно так:

Nested Loop  (cost=2.39..200.79 rows=79 width=52) (actual time=0.021..0.165 rows=115 loops=1)
  ->  Bitmap Heap Scan on public.customers  (cost=1.97..20.71 rows=13 width=28) (actual time=0.014..0.023 rows=15 loops=1)
        Recheck Cond: ((customers.zip_code)::text = '80807'::text)
        ->  Bitmap Index Scan on customers_zip_code_idx  (cost=0.00..1.96 rows=13 width=0) (actual time=0.010..0.010 rows=15 loops=1)
              Index Cond: ((customers.zip_code)::text = '80807'::text)
  ->  Index Scan using idx_orders_cust_id on public.orders o  (cost=0.42..13.84 rows=8 width=24) (actual time=0.003..0.008 rows=8 loops=15)
        Index Cond: (o.customer_id = customers.id)

Как видите, ссылка на функцию отсутствует (план запроса без функции выглядит примерно так же).

Если функция не встроенная (например, потому что она не была объявлена ​​stable или потому что это функция PL / pgSQL, а не функция SQL), план будет выглядеть примерно так:

Nested Loop  (cost=0.68..139.94 rows=77 width=110) (actual time=0.710..0.862 rows=115 loops=1)
  ->  Function Scan on public.customers_in_zip c  (cost=0.25..0.26 rows=10 width=86) (actual time=0.696..0.697 rows=15 loops=1)
        Function Call: customers_in_zip('42'::character varying)
        Buffers: shared hit=18
  ->  Index Scan using idx_orders_cust_id on public.orders o  (cost=0.42..13.96 rows=8 width=24) (actual time=0.004..0.009 rows=8 loops=15)
        Output: o.id, o.customer_id, o.order_date, o.amount, o.sales_person_id
        Index Cond: (o.customer_id = c.id)

Из вашего описания кажется, что вы не имеете в виду такого рода "встраивание", а скорее, вызывается ли скалярная функция только один раз, если она не зависит от значений, взятых из строки, например ::

select col1, some_function(), col2
from some_table;

Если some_function() объявлено immutable, оно будет вызвано только один раз.

Цитата из руководства

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

Это не то, что вы можете видеть непосредственно в плане выполнения, но следующее продемонстрирует это:

create function expensive_scalar(p_some_input integer)
  returns integer
as 
$$
begin
  perform pg_sleep(10);
  return p_some_input * 2;
end;  
$$
language plpgsql
<b>IMMUTABLE</b>;

perform pg_sleep(10); заставляет функцию занять 10 секунд для выполнения. Следующий запрос будет вызывать эту функцию сто раз:

select i, expensive_scalar(2)
from generate_series(1,100) i;

Но выполнение занимает чуть более 10 секунд, что ясно показывает, что функция вызывается только один раз.


Насколько я знаю, Postgres также будет кэшировать результаты для функций, помеченных как stable во время выполнения одного оператора (для тех же значений ввода).

Это немного сложнее показать, хотя. Обычно вы можете сделать это, поместив в функцию raise notice операторов (эквивалентных Postgres print) и посмотрите, как часто они печатаются.

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