CREATE OR REPLACE 如何在已經存在的情況下失敗?
DETAIL: Key (proname, proargtypes, pronamespace)=(rand, , 2200) already exists.: CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
這到底怎麼可能?
CREATE OR REPLACE
已經存在怎麼會失敗?這是在一個 docker 容器中,完全原始的環境,這是唯一的程式碼執行。是的,它可能會反復執行,但這OR REPLACE
就是存在的原因。我不明白。這是完整的命令:if (!$connection->query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) { $connection->query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS \'SELECT random();\' LANGUAGE \'sql\''); }
中的
OR REPLACE
子句CREATE FUNCTION
並不是為了無縫並行執行,它是為了避免在我們只想更新主體時刪除函式。從文件:如果您刪除然後重新創建一個函式,則新函式與舊函式不是同一個實體;您將不得不刪除引用舊功能的現有規則、視圖、觸發器等。使用 CREATE OR REPLACE FUNCTION 更改函式定義,而不會破壞引用該函式的對象。
現在,如果多個會話
CREATE OR REPLACE FUNCTION
並行發出相同的問題,則確實存在潛在的競爭條件,該條件依賴於 上的唯一索引來處理(proname, proargtypes, pronamespace)
,並作為錯誤返回給呼叫者。問題中提到的錯誤發生在例如:
- session #1 啟動
CREATE OR REPLACE
並發現該函式不存在,因此它創建它,插入pg_proc
並鎖定索引中的相應(proname, proargtypes, pronamespace)
條目。- 會話#2 開始
CREATE OR REPLACE
,發現函式不存在(因為#1 尚未送出)並被置於等待索引鎖。- 會話 #1 送出。
- 會話 #2 嘗試插入並失敗。
請注意,不使用顯式 BEGIN/COMMIT 不會改變競爭條件:BEGIN/COMMIT 對只是隱含在每個 SQL 語句周圍。
要讓這個並行序列無縫工作,
OR REPLACE
需要CREATE FUNCTION
什麼ON CONFLICT
是 toINSERT
,但事實並非如此。為了處理可能並行執行的衝突或相同的 DDL,您可能應該在序列的開頭使用顯式鎖以將其作為一個整體放在關鍵部分中。建議鎖可能用於此。或者您需要使用圍繞單個語句的保存點來實施“重試失敗”策略。