支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1

44.7. 顯式子交易 #

第 44.6.2 節所述,從資料庫存取引起的錯誤中恢復,可能會導致一種不良情況,即某些操作在其中一個操作失敗之前成功,並且在從該錯誤中恢復之後,資料會處於不一致的狀態。PL/Python 以顯式子交易的形式為此問題提供了一個解決方案。

44.7.1. 子交易上下文管理器 #

考慮一個實現兩個帳戶之間轉帳的函式

CREATE FUNCTION transfer_funds() RETURNS void AS $$
try:
    plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
    plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
    result = "error transferring funds: %s" % e.args
else:
    result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

如果第二個 UPDATE 陳述式導致引發異常,則此函式將報告錯誤,但第一個 UPDATE 的結果仍將被提交。換句話說,資金將從 Joe 的帳戶中提取,但不會轉移到 Mary 的帳戶。

為了避免此類問題,您可以將您的 plpy.execute 呼叫封裝在顯式子交易中。plpy 模組提供了一個輔助物件來管理顯式子交易,該物件使用 plpy.subtransaction() 函式建立。由此函式建立的物件實現了上下文管理器介面。使用顯式子交易,我們可以將我們的函式重寫為

CREATE FUNCTION transfer_funds2() RETURNS void AS $$
try:
    with plpy.subtransaction():
        plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
        plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
    result = "error transferring funds: %s" % e.args
else:
    result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

請注意,仍然需要使用 try/except。否則,該異常將傳播到 Python 堆疊的頂部,並導致整個函式中止,並出現 PostgreSQL 錯誤,因此 operations 表格將不會插入任何列。子交易上下文管理器不捕捉錯誤,它僅確保在其範圍內執行的所有資料庫操作都將原子地提交或回滾。子交易區塊的回滾會在任何類型的異常退出時發生,而不僅僅是由於資料庫存取引起的錯誤。在顯式子交易區塊中引發的常規 Python 異常也會導致子交易回滾。

提交更正

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