установить уровень изоляции хранимых процедур postgresql

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

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

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 предназначены только для группировка; они не начинаются и не заканчиваются торговая операция. Функции и триггер процедуры всегда выполняются внутри сделки установлено наружное запрос - они не могут начать или совершить эта сделка, так как будет нет контекста для их выполнения.

наиболее очевидным подходом ко мне было бы использование 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;

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

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

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

The руководство и говорит:

уровень изоляции транзакции не может изменить после первого запроса или инструкция Data-modification (выбрать, Вставка, удаление, обновление, выборка или Копия) транзакции была выполненный.

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

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

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

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

4 ответов


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

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


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

Это не удается:

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 / languages специфичны для платформы.


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

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

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

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


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

BEGIN TRAN

SELECT 1;
SELECT my_proc(99);

ROLLBACK TRAN;

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

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