Восстановление после ошибок, вызванных доступом к базе данных, как описано в Раздел 5.9.6.2 может привести к нежелательной ситуации, когда часть операций завершается успешно до возникновения сбоя в одной из них, и после восстановления после этой ошибки данные остаются в несогласованном состоянии. 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/except по-прежнему
требуется. В противном случае исключение распространится до вершины стека Python, что приведет к аварийному завершению всей функции с Digital Q.DataBase ошибкой, и в таблицу
operations не будет добавлено ни одной строки. Диспетчер контекста подтранзакции не перехватывает ошибки; он лишь гарантирует, что все операции с базой данных, выполненные в его области действия, будут атомарно зафиксированы или откачены. Откат блока подтранзакции происходит при любом выходе по исключению, а не только при ошибках, возникающих при доступе к базе данных. Обычное исключение Python, возбужденное внутри явного блока подтранзакции, также приведет к откату этой подтранзакции.