2017-02-09 104 views
2

我有一個表ContentAddressedFiles其中列hash,sizeextension的組合是UNIQUE。我想創建一個被調用的存儲過程,它會使用給定的值向表中插入一條新記錄。如果這些值的記錄已經存在,我想只返回該現有記錄。這裏是我的方法:無法選擇UNIQUE VIOLATION的原因

CREATE OR REPLACE FUNCTION INIT_CAF(id_in_case_of_new UUID, _hash VARCHAR(255), _size INTEGER, _extension VARCHAR(255), _mimeType VARCHAR(255)) 
RETURNS "ContentAddressedFiles" 
AS $$ 
DECLARE 
    caf "ContentAddressedFiles"%ROWTYPE; 
BEGIN 

    INSERT INTO "ContentAddressedFiles" (id, hash, size, extension, "mimeType", "createdAt", "updatedAt") 
    VALUES(id_in_case_of_new, _hash, _size, _extension, _mimeType, NOW(), NOW()) RETURNING * INTO caf; 

    RETURN caf; 

EXCEPTION WHEN unique_violation THEN 

    SELECT * FROM "ContentAddressedFiles" INTO caf WHERE "hash" = _hash AND "size" = _size AND "extension" = _extension; 

    IF NOT FOUND THEN 
    RAISE EXCEPTION 'This should never happen.'; 
    END IF; 

    RETURN caf; 

END; 
$$ LANGUAGE plpgsql; 

然而,當我從打電話併發事務的過程中,我始終得到異常:

EXCEPTION: This should never happen. 

這怎麼可能?該程序似乎並不能夠前SELECT發生故障的INSERT的原因(這不是衝突,它是<hash, size, extension>只是元組中的id

+0

讓你看到升起exveption和空值而不是預期的行?..請加你輸出的問題 –

+0

是的,正是我得到的異常,當我從不同的事務同時運行的程序。 – DeX3

+0

http://stackoverflow.com/questions/6722344/select-or-insert-a-row-in-one-command –

回答

1

的問題如何避免這個問題的答案爲的意見。我「會在這裏解釋爲什麼的PostgreSQL中所觀察到的方式行事。

的原因是INSERT和功能的SELECT聲明查看數據庫的不同快照(州),因爲事務與默認運行隔離級別爲READ COMMITTED。在隔離級別,每條語句都會獲得新的數據庫快照。

所觀察到的行爲的解釋必須是併發事務刪除或修改失敗INSERT及以下SELECT語句之間的行,這樣導致約束違反了INSERT行不再存在時SELECT運行。

有處理問題的兩種方法:

  • 選擇一個更高的隔離級別:那麼這兩個語句會看到數據庫的同一快照,並防止INSERT將由找到行儘管它在此期間已經改變,但是該SELECT。這不是問題,它只是意味着整個事務邏輯上發生在拍攝快照時。

  • 將這兩個語句作爲單個語句與CTE一起運行,就像評論推薦中給出的解決方案一樣。然後他們也會看到相同的數據庫快照。

+0

是的,你是對的,這是最有可能的情況下(即使競爭條件的窗口很小)。至少對於'VOLATILE'功能;從'STABLE'中,快照是不同的,但是該類別無論如何都不允許使用INSERT。也許這個快照的一個很好的例子是[原始UPSERT示例](https://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING)如何工作。 – pozs

相關問題