Postgres сохраненная функция проверки ввода-вывода, интерпретация результатов синхронизации - PullRequest
1 голос
/ 05 февраля 2020

Отвечая на еще один вопрос, Клин продемонстрировал простой способ провести несколько временных тестов. Вопрос в том, насколько дороги исключения? В документации и других местах упоминается, что PL / Pg SQL медленнее, чем SQL для хранимых функций, и что EXCEPTION стоит дорого. У меня нет интуиции в отношении производительности Postgres в этих ситуациях, и я решил попробовать несколько сравнений. Клин показал, как использовать (замечательную) функцию generate_series(), чтобы упростить эту задачу.

И вот необходимый преамбул:

  • I ругаться Я не начинаю борьбу за тесты скорости. У меня меньше , чем нет интереса к этому.

  • Это случайные, искусственные тесты. Я просто пытаюсь понять, как разные стили сравниваются друг с другом. По сути, каковы основные издержки c в хранимых функциях для различных подходов к проверке ввода.

  • SQL и PL / Pg SQL не являются взаимозаменяемыми, поэтому это не совсем Справедливо сравнить их 1: 1. Если вы можете сделать что-то в чистом виде SQL, отлично. Но это не всегда возможно.

  • Эти тесты запускают каждую функцию по 1 000 000 раз каждый, чтобы усилить в абсолютном выражении незначительные различия во времени выполнения.

  • Числа округляются до ближайших 10 ... и даже в этом случае вводят в заблуждение. С современными ЦП и современными ОС, получение нескольких% вариабельности по сравнению с «одинаковыми» прогонами является нормальным.

Как ни важно, тесты не сравнимы напрямую, как подпрограммы несколько разные вещи. Итак, если вы заинтересованы в этом вопросе, вы должны прочитать код. Тесты пытаются сравнить несколько вещей:

  • SQL против PL / Pg SQL для простой операции.
  • Стоимость неиспользованного EXCEPTION блока.
  • Стоимость неиспользованного IF...ELSE...END IF блока.
  • Стоимость EXCEPTION блока и RAISE для проверки входного параметра.
  • Стоимость * Блок 1050 * и RAISE для проверки входного параметра.
  • Стоимость ограничения на основе DOMAIN для вызовов короткого замыкания с неверным входным параметром.

Вот Сводка времени выполнения для 1 000 000 итераций, каждая из которых использует PG 12.1:

Language    Function                     Error     Milliseconds
SQL         test_sql                     Never             580
PL/PgSQL    test_simple                  Never            2250
PL/PgSQL    test_unused_exception_block  Never            4200
PL/PgSQL    test_if_that_never_catches   Never            2600
PL/PgSQL    test_if_that_catches         Never             310
PL/PgSQL    test_if_that_catches         Every time       2750
PL/PgSQL    test_exception_that_catches  Never            4230
PL/PgSQL    test_exception_that_catches  Every time       3950
PL/PgSQL    test_constraint              Never             310
PL/PgSQL    test_constraint              Every time       2380

Примечание. Я изменил число итераций в тестах по отлову ограничений и, да, он меняется. Так что не похоже, что l oop ломается при первой ошибке.

Если вы запустите код самостоятельно, вы получите разные времена ... и изменчивость между несколькими запусками довольно высока. Так что, я думаю, не о тех числах, которые вы можете использовать для большего, чем о смысле вещей.

Кто-нибудь видит что-то совершенно не похожее на результаты здесь, или как я их рассчитал? В моем конкретном случае все вышеприведенные цифры читаются как «абсолютно хорошо, это не будет иметь никакого значения в реальном мире». Вы должны выполнить эти вещи более 1000 раз, чтобы получить миллисекунду разницы, давай или бери. Я смотрю на проверку ошибок для методов, которые вызываются несколько ... а не миллион раз в al oop. Мои функции будут тратить свое время на выполнение реальной работы, например, поиск, издержки любого из подходов, которые я пробовал, пахнет несущественно. Для меня победитель выглядит как test_if_that_catches. А именно, IF в начале BEGIN, который перехватывает неверные данные и затем использует RAISE для возврата отчета. Это хорошее совпадение с тем, как мне нравится структурировать методы в любом случае, оно доступно для чтения и так просто вызывать пользовательские исключения.

Я перечислю функции, а затем проверю код.

--------------------------------------------
-- DOMAIN: text_not_empty
--------------------------------------------
DROP DOMAIN IF EXISTS text_not_empty;

CREATE DOMAIN text_not_empty AS
    text
    NOT NULL
    CHECK (value <> '');

COMMENT ON DOMAIN text_not_empty IS
    'The string must not be empty';

--------------------------------------------
-- FUNCTION test_sql()
--------------------------------------------
drop function if exists test_sql();
create or replace function test_sql()
returns int as $$

select 1;
$$
LANGUAGE sql;

--------------------------------------------
-- FUNCTION test_simple()
--------------------------------------------
drop function if exists test_simple();
create or replace function test_simple()
returns int language plpgsql as $$
begin
    return 1;
end $$;

--------------------------------------------
-- FUNCTION test_unused_exception_block()
--------------------------------------------
drop function if exists test_unused_exception_block();
create or replace function test_unused_exception_block()
returns int language plpgsql as $$
begin
    return 1;
exception when others then
    raise exception 'ugh';
-- note that any exception is never trapped
-- anyway the function is much more expensive
-- see execution time in query plans
end $$;

--------------------------------------------
-- FUNCTION test_if_that_never_catches()
--------------------------------------------
drop function if exists test_if_that_never_catches();
create or replace function test_if_that_never_catches()
returns int language plpgsql as $$
begin
if 1 > 2 then
    raise exception 'You have an unusually high value for 1';
    -- This never happens, I'm following Klin's previous example,
    -- just trying to measure the overhead of the if...then..end if.
end if;

    return 1;
end $$;

--------------------------------------------
-- FUNCTION test_if_that_catches()
--------------------------------------------
drop function if exists test_if_that_catches(text_not_empty);
create or replace function test_if_that_catches(text_not_empty)
returns int language plpgsql as $$
begin
if $1 = '' then
    raise exception 'The string must not be empty';
end if;

    return 1;
end $$;

--------------------------------------------
-- FUNCTION test_exception_that_catches()
--------------------------------------------
drop function if exists test_exception_that_catches(text);
create or replace function test_exception_that_catches(text)
returns int language plpgsql as $$
begin
    return 1;
exception when others then
    raise exception 'The string must not be empty';
end $$;

--------------------------------------------
-- FUNCTION test_constraint()
--------------------------------------------
drop function if exists test_constraint(text_not_empty);
create or replace function test_constraint(text_not_empty)
returns int language plpgsql as $$
begin
    return 1;
end $$;


--------------------------------------------
-- Tests
--------------------------------------------
-- Run individually and look at execution time

explain analyse
select sum(test_sql())
from generate_series(1, 1000000);

explain analyse
select sum(test_simple())
from generate_series(1, 1000000);

explain analyse
select sum(test_unused_exception_block())
from generate_series(1, 1000000);

explain analyse
select sum(test_if_that_never_catches())
from generate_series(1, 1000000);

explain analyse
select sum(test_if_that_catches('')) -- Error thrown on every case
from generate_series(1, 1000000);

explain analyse
select sum(test_if_that_catches('a')) -- Error thrown on no cases
from generate_series(1, 1000000);

explain analyse
select sum(test_exception_that_catches(''))-- Error thrown on every case
from generate_series(1, 1000000);

explain analyse
select sum(test_exception_that_catches('a')) -- Error thrown on no cases
from generate_series(1, 1000000);

explain analyse
select sum(test_constraint('')) -- Error thrown on no cases
from generate_series(1, 1000000);

explain analyse
select sum(test_constraint('a')) -- Error thrown on no cases
from generate_series(1, 1000000); 

1 Ответ

0 голосов
/ 05 февраля 2020

Ваши тесты выглядят нормально для меня, если все, что вы хотите сравнить, это скорость различных методов проверки правильности вводимых данных. Неудивительно, что методы, которые избегают вызова функции вообще, выигрывают.

Я согласен с вами, что разница в основном не имеет значения. Проверка входных данных - это не то, что решит, будут ли ваши функции эффективными или нет, это затеряется в шуме, если функция сделает какую-то реальную работу.

Ваши усилия доблестны, но ваше время может быть потрачено лучше на настройку SQL операторы, которые должна выполнить функция.

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