43.7. Явные подтранзакции#
43.7. Явные подтранзакции #
Восстановление после ошибок, вызванных доступом к базе данных, описанных в Раздел 43.6.2, может привести к нежелательной ситуации, когда некоторые операции успешно выполняются до того, как одна из них завершится с ошибкой, и после восстановления от этой ошибки данные остаются в несогласованном состоянии. PL/Python предлагает решение этой проблемы в виде явных подтранзакций.
43.7.1. Менеджеры контекста подтранзакций #
Рассмотрим функцию, которая реализует перевод между двумя счетами:
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 и вызовет завершение всей функции с ошибкой Tantor BE, так что в таблицу operations
не будет вставлена ни одна строка. Контекстный менеджер подтранзакции не перехватывает ошибки, он только гарантирует, что все операции с базой данных, выполненные в его области, будут атомарно зафиксированы или откатаны. Откат блока подтранзакции происходит при любом виде выхода с исключением, а не только при ошибках, вызванных доступом к базе данных. Обычное исключение Python, вызванное внутри явного блока подтранзакции, также приведет к откату подтранзакции.