2012-01-03 20 views
3

讓我們假裝有一個tags表,其中有一個名爲name的獨特字段。如何使這兩個同時事務在任何時候都成功,而不會導致違反唯一性?

我有我進行選擇,以查看是否有特定名稱的標籤存在一個事務,如果不是我創建它:

START TRANSACTION; 
SELECT * FROM TAGS WHERE NAME = "FOO"; 
-- IF A TAG NAMED "FOO" DIDN'T EXIST THEN 
INSERT INTO TAGS VALUES("FOO"); 
COMMIT; 

當兩個客戶端在運行此交易默認隔離級別(可重複讀),該交錯會導致他們中的一個失敗,唯一性衝突:

START TRANSACTION; 
               START TRANSACTION; 
SELECT * FROM TAGS WHERE NAME = "FOO"; 
               SELECT * FROM TAGS WHERE NAME = "FOO"; 
-- IF A TAG NAMED "FOO" DIDN'T EXIST THEN 
INSERT INTO TAGS VALUES("FOO"); 
               -- IF A TAG NAMED "FOO" DIDN'T EXIST THEN 
               INSERT INTO TAGS VALUES("FOO"); 
COMMIT; 
               COMMIT; 

我想如果我設置的隔離級別設置爲序列化,我能避免這種情況,但我注意到,相同的交織將導致死鎖。

我該如何修改事務,以避免由於唯一性約束違規而失敗?

根據記錄,這是Ruby on Rails的(ActiveRecord的)代碼對應於這樣的場景:

class Tag < ActiveRecord::Base 
    def self.create_tag(name) 
    transaction do 
     # setting isolation level to serializable leads to a deadlock 
     # Tag.connection.execute("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE") 
     gets 
     tag = Tag.find_by_name(name) 
     if tag.nil? 
     gets 
     Tag.create!(:name => name) 
     end 
    end 
    end 
end 
+0

一種offtopic,但更好的使用從名稱='foo'的標籤中選擇名稱;這將是索引覆蓋的查詢,它比訪問表的速度更快 – 2012-01-04 21:35:42

+0

您可以執行插入...選擇嗎?這[使用互斥表](http://www.xaprb.com/blog/2005/09/25/insert-if-not-exists-queries-in-mysql/) – 2012-01-04 21:50:51

回答

0

看起來像這是一種競爭條件,Rails沒有任何內置的方法來避免它開箱即用。

2

我不是一個Rails的傢伙,但你可以從一個SQL的角度解決問題使用單個查詢並將IGNORE添加到插入,如果標記已經存在,則不會失敗。

INSERT IGNORE INTO TAGS VALUES("FOO"); 
+0

+1正確的答案。 – 2012-01-03 11:39:00

+0

感謝您的回覆邁克爾。我不知道「IGNORE」,但我認爲它不能用於我的情況。有沒有辦法解決這個使用隔離級別?就算真正完全序列化的交易一樣?或者用'FOR UPDATE'或'LOCK IN SHARE MODE'? – 2012-01-03 11:40:29

+0

希望有些Rails guru可以插話,因爲我猜測這裏有一個更習慣的解決方案。 – 2012-01-03 11:40:31

相關問題