41.9. Явные подтранзакции в PL/Tcl#

41.9. Явные подтранзакции в PL/Tcl

41.9. Явные подтранзакции в PL/Tcl #

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

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

CREATE FUNCTION transfer_funds() RETURNS void AS $$
    if [catch {
        spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
        spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
    } errormsg] {
        set result [format "error transferring funds: %s" $errormsg]
    } else {
        set result "funds transferred successfully"
    }
    spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
$$ LANGUAGE pltcl;

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

Для обработки таких случаев вы можете обернуть несколько операций с базой данных в явную подтранзакцию, которая будет успешно завершена или откатана в целом. PL/Tcl предоставляет команду subtransaction для управления этим. Можно переписать нашу функцию следующим образом:

CREATE FUNCTION transfer_funds2() RETURNS void AS $$
    if [catch {
        subtransaction {
            spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
            spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
        }
    } errormsg] {
        set result [format "error transferring funds: %s" $errormsg]
    } else {
        set result "funds transferred successfully"
    }
    spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
$$ LANGUAGE pltcl;

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

Возврат явной подтранзакции происходит при любой ошибке, сообщенной из содержащегося кода Tcl, а не только из ошибок, возникающих из базы данных. Таким образом, обычное исключение Tcl, вызванное внутри команды subtransaction, также приведет к откату подтранзакции. Однако, ненормальные выходы из содержащегося кода Tcl (например, из-за return) не вызывают отката.