如第 44.6.2 節所述,從資料庫存取引起的錯誤中恢復,可能會導致一種不良情況,即某些操作在其中一個操作失敗之前成功,並且在從該錯誤中恢復之後,資料會處於不一致的狀態。PL/Python 以顯式子交易的形式為此問題提供了一個解決方案。
考慮一個實現兩個帳戶之間轉帳的函式
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 異常也會導致子交易回滾。
如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的內容,請使用此表格回報文件問題。