43.8で説明されているように、データベースアクセスによって生じたエラーからの回復により、操作のうちいくつかが失敗する前に他の操作が成功し、エラーからの回復後、データの一貫性が失われた望ましくない状態になってしまう可能性があります。 PL/Tclは明示的なトランザクションの手法でこの問題を解決する手段を提供しています。
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;
ふたつ目のUPDATE
文で例外が発生する結果になると、この関数は失敗を記録しますが、それにもかかわらず、最初のUPDATE
はコミットされます。
言い換えると、Joeのアカウントから資金が引き出されたのに、Maryのアカウントには転送されません。
これは、それぞれの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のコードの中でエラーが報告された際だけでなく、データベースアクセスに起因するエラーの際にも起こります。
ですから、subtransaction
コマンド内の内側で起こった通常のTcl例外は、サブトランザクションのロールバックも引き起こします。
しかし、Tclコードからのエラーによらない脱出(たとえばreturn
によるもの)は、ロールバックをもたらしません。