2025年9月25日: PostgreSQL 18 釋出!
支援的版本:當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10

42.9. PL/Tcl 中的顯式子事務 #

第 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)不會導致回滾。

提交更正

如果您在文件中發現任何不正確的內容、與您對特定功能的體驗不符的內容或需要進一步澄清的內容,請使用此表單報告文件問題。