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

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, вызванное внутри явного блока подтранзакции, также приведет к откату подтранзакции.