Отвечая на еще один вопрос, Клин продемонстрировал простой способ провести несколько временных тестов. Вопрос в том, насколько дороги исключения? В документации и других местах упоминается, что 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);