Recovering from errors caused by database access as described in <xref linkend="pltcl-error-handling"/> can lead to an undesirable situation where some operations succeed before one of them fails, and after recovering from that error the data is left in an inconsistent state. PL/Tcl offers a solution to this problem in the form of explicit subtransactions. 42.8で説明されているように、データベースアクセスによって生じたエラーからの回復により、操作のうちいくつかが失敗する前に他の操作が成功し、エラーからの回復後、データの一貫性が失われた望ましくない状態になってしまう可能性があります。 PL/Tclは明示的なトランザクションの手法でこの問題を解決する手段を提供しています。
Consider a function that implements a transfer between two accounts: 2つのアカウントの間の送金を実装する関数を考えます。
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;
If the second <command>UPDATE</command> statement results in an
exception being raised, this function will log the failure, but
the result of the first <command>UPDATE</command> will
nevertheless be committed. In other words, the funds will be
withdrawn from Joe's account, but will not be transferred to
Mary's account. This happens because each <function>spi_exec</function>
is a separate subtransaction, and only one of those subtransactions
got rolled back.
ふたつ目のUPDATE
文で例外が発生する結果になると、この関数は失敗を記録しますが、それにもかかわらず、最初のUPDATE
はコミットされます。
言い換えると、Joeのアカウントから資金が引き出されたのに、Maryのアカウントには転送されません。
これは、それぞれのspi_exec
が別々のサブトランザクションになっていて、そのうち一つのサブトランザクションだけがロールバックされるからです。
To handle such cases, you can wrap multiple database operations in an
explicit subtransaction, which will succeed or roll back as a whole.
PL/Tcl provides a <function>subtransaction</function> command to manage
this. We can rewrite our function as:
このような状況に対応するには、複数のデータベース操作を、全体が成功するか、あるいは失敗する明示的なサブトランザクションで包みます。
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;
Note that use of <function>catch</function> is still required for this
purpose. Otherwise the error would propagate to the top level of the
function, preventing the desired insertion into
the <structname>operations</structname> table.
The <function>subtransaction</function> command does not trap errors, it
only assures that all database operations executed inside its scope will
be rolled back together when an error is reported.
この目的のために、catch
が必要であることに注意してください。
そうでないと、エラーが関数のトップレベルまで伝搬し、期待したようなoperations
テーブルへの挿入が阻害されてしまいます。
subtransaction
コマンドはエラーを補足しません。
エラーが報告された際に、スコープの内側で実行されたすべてのデータベース操作がロールバックされることを保証するだけです。
A rollback of an explicit subtransaction occurs on any error reported
by the contained Tcl code, not only errors originating from database
access. Thus a regular Tcl exception raised inside
a <function>subtransaction</function> command will also cause the
subtransaction to be rolled back. However, non-error exits out of the
contained Tcl code (for instance, due to <function>return</function>) do
not cause a rollback.
明示的なサブトランザクションのロールバックは、Tclのコードの中でエラーが報告された際だけでなく、データベースアクセスに起因するエラーの際にも起こります。
ですから、subtransaction
コマンド内の内側で起こった通常のTcl例外は、サブトランザクションのロールバックも引き起こします。
しかし、Tclコードからのエラーによらない脱出(たとえばreturn
によるもの)は、ロールバックをもたらしません。