Перейти к основному содержимому

Явные подтранзакции

примечание

Эта страница переведена при помощи нейросети GigaChat.

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

Менеджеры контекста подтранзакций

Рассмотрим функцию, которая реализует передачу между двумя счетами:

CREATE FUNCTION transfer_funds() RETURNS void AS $$
try:
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

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

Чтобы избежать таких проблем, можно обернуть свои вызовы plpy.execute в явную подтранзакцию. Модуль plpy предоставляет вспомогательный объект для управления явными подтранзакциями, который создается с помощью функции plpy.subtransaction(). Объекты, созданные этой функцией, реализуют интерфейс диспетчера контекста. Используя явные под транзакции, можно переписать функцию следующим образом:

CREATE FUNCTION transfer_funds2() RETURNS void AS $$
try:
with plpy.subtransaction():
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

Обратите внимание, что использование try/catch по-прежнему требуется. В противном случае исключение будет передано на вершину стека Python и вызовет прерывание всей функции с ошибкой PostgreSQL, так что в таблицу operations не будет вставлена ни одна строка. Контекстный менеджер подтранзакции не перехватывает ошибки, он только гарантирует, что все операции с базой данных, выполняемые в его области действия, будут атомарно подтверждены или отменены. Откат блока под транзакции происходит при любом типе исключения выхода, а не только тех, которые вызваны ошибками, возникающими из-за доступа к базе данных. Обычное исключение Python, вызванное внутри явного блока под транзакции, также приведет к откату под транзакции.