установить уровень изоляции для хранимых процедур postgresql - PullRequest
20 голосов
/ 08 июня 2011

Надеюсь, простой вопрос, но на который я так и не нашел достойного ответа.Я достоверно проинформирован о том, что хранимые процедуры (определяемые пользователем функции БД) в PostgreSQL (в частности, версия 9.0.4) по своей сути транзакционные, поскольку они вызываются через оператор SELECT, который сам по себе является транзакцией.Так как же выбрать уровень изоляции хранимой процедуры?Я полагаю, что в других СУБД требуемый транзакционный блок будет заключен в блок START TRANSACTION, для которого требуемый уровень изоляции является необязательным параметром.

В качестве конкретного выдуманного примера, скажем, я хочу сделать это:

CREATE FUNCTION add_new_row(rowtext TEXT)
RETURNS VOID AS 
$$
BEGIN
        INSERT INTO data_table VALUES (rowtext);
        UPDATE row_counts_table SET count=count+1;
END;
$$  
LANGUAGE plpgsql
SECURITY DEFINER;

И представьте, что я хочу убедиться, что эта функция всегда выполняется как сериализуемая транзакция (да, да, PostgreSQL SERIALIZABLE некорректно сериализуем, но это не главное).Я не хочу требовать, чтобы он назывался

START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT add_new_row('foo');
COMMIT;

Итак, как мне ввести требуемый уровень изоляции в функцию?Я считаю, что не могу просто указать уровень изоляции в операторе BEGIN, так как в руководстве написано

Важно не путать использование BEGIN / END для группировки операторовв PL / pgSQL с одноименными командами SQL для управления транзакциями.PL / pgSQL BEGIN / END предназначены только для группировки;они не начинают и не заканчивают транзакцию.Функции и процедуры триггера всегда выполняются в транзакции, установленной внешним запросом - они не могут запустить или зафиксировать эту транзакцию, поскольку не было бы контекста, в котором они могли бы выполняться.

Наиболее очевидный подход кЯ бы хотел использовать SET TRANSACTION где-нибудь в определении функции, например:

CREATE FUNCTION add_new_row(rowtext TEXT)
RETURNS VOID AS 
$$
BEGIN
        SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
        INSERT INTO data_table VALUES (rowtext);
        UPDATE row_counts_table SET count=count+1;
END;
$$  
LANGUAGE plpgsql
SECURITY DEFINER;

Хотя это и будет принято, неясно, на что я могу положиться, чтобы это работало.Документация для SET TRANSACTION гласит:

Если SET TRANSACTION выполняется без предшествующего START TRANSACTION или BEGIN, это, похоже, не будет иметь никакого эффекта, поскольку транзакция немедленно завершится.

Что вызывает у меня недоумение, поскольку, если я вызову одиночный оператор SELECT add_new_row('foo');, я ожидаю (при условии, что я не отключил автокоммит) SELECT будет запущен как однострочная транзакция с сеансомуровень изоляции по умолчанию.

В руководстве также сказано:

Уровень изоляции транзакции не может быть изменен после первого запроса или оператора изменения данных (SELECT, INSERT, DELETE, UPDATE, FETCH или COPY) транзакции были выполнены.

Так что же происходит, если функция вызывается из транзакции с более низким уровнем изоляции, например:

START TRANSACTION ISOLATION LEVEL READ COMMITTED;
UPDATE row_counts_table SET count=0;
SELECT add_new_row('foo');
COMMIT;

Для дополнительного вопроса: имеет ли значение язык функции?Можно ли установить уровень изоляции в PL / pgSQL иначе, чем в обычном SQL?

Я фанат стандартов и документированных лучших практик, поэтому любые достойные ссылки будут оценены.

Ответы [ 4 ]

17 голосов
/ 08 июня 2011

Вы не можете этого сделать.

То, что вы могли бы сделать, это заставить свою функцию проверить текущий уровень изоляции транзакции и прервать ее, если она не та, которую вы хотите.Вы можете сделать это, запустив SELECT current_setting('transaction_isolation') и проверив результат.

1 голос
/ 08 июня 2011

Язык функции не имеет значения.

Это не удалось:

test=# create function test() returns int as $$
  set transaction isolation level serializable;
  select 1;
$$ language sql;
CREATE FUNCTION
test=# select test();
ERROR:  SET TRANSACTION ISOLATION LEVEL must be called before any query
CONTEXT:  SQL function "test" statement 1

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

Я фанат стандартов

PL / языки зависят от платформы.

0 голосов
/ 08 июня 2011

В PG ваши процедуры не являются отдельными транзакциями. То есть хранимая процедура принимает участие в существующей транзакции.

BEGIN TRAN

SELECT 1;
SELECT my_proc(99);

ROLLBACK TRAN;

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

Один из вариантов - настроить сервер для работы в изоляции, которую вы в основном хотите использовать, и выполнить SET для крайних случаев, когда он отличается от настроек вашего сервера.

0 голосов
/ 08 июня 2011

Изоляция транзакции означает, что к изменениям, внесенным в другие параллельные транзакции, вы можете получить доступ.

Если вы хотите сериализовать выполнение, вы должны использовать блокировки.

Вы можете использовать после триггера строки и счетчика обновлений,«UPDATE row_counts_table» заблокирует таблицу, и все транзакции будут сериализованы. Это медленно.

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

...