如第 42.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
的結果仍然會被提交。 換句話說,資金將從 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
)不會導致回滾。
如果您在文件中發現任何不正確、與特定功能的使用經驗不符或需要進一步澄清的地方,請使用此表格來報告文件問題。