如第 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 異常也會導致子交易回滾。
如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用此表單來報告文件問題。