2012-05-17 62 views
15

我試圖強制在我的表字段之一的值的唯一性。更改表格不是一個選項。我需要使用ActiveRecord有條件地插入一行到表中,但我關心同步。Rails的競爭條件first_or_create

是否first_or_create在Rails ActiveRecord中預防競爭狀態?

這是從GitHub的first_or_create的源代碼:

def first_or_create(attributes = nil, options = {}, &block) 
    first || create(attributes, options, &block) 
end 

是否有可能重複的條目將導致數據庫由於有多個進程同步的問題?

+2

AR充滿了這樣的競爭條件。 – dbenhur

+0

請參閱(SO dup)[我如何避免Rails應用程序中的競態條件?](http://stackoverflow.com/questions/3037029/how-do-i-avoid-a-race-condition-in-my -rails-app)和Rails Cookbook [避免使用樂觀鎖定的競態條件](http://underpop.free.fr/r/ruby-on-rails/cookbook/I_0596527314_CHP_3_SECT_19.html) – dbenhur

+0

@dbenhur - 我無法使用樂觀鎖定,因爲它涉及到向表中添加一個字段。我的一個條件是我不能添加一個字段,所以它不是重複的。 –

回答

5

是的,這是可能的。

您可以顯着減少與optimisticpessimistic locking發生衝突的可能性。當然,樂觀鎖定需要在表中添加一個字段,而悲觀鎖定也不會擴展 - 此外,這取決於您的數據存儲的功能。

我不確定你是否需要額外的保護,但它是可用的。

+0

我無法使用樂觀鎖定,因爲修改表格不是一個選項。悲觀鎖定的問題是,我無法通過Rails指定我需要的鎖定隔離級別,而無需插入InnoDB特定的語法。儘管可能存在重複的行,但我最終只是使用'first_or_create'。 雖然我認爲我可以在ActiveRecord中使用驗證助手來確保唯一性。 –

+2

他們遭受相同的競爭條件 –

17

鋼軌4 documentation for find_or_create_by提供了可以用於這種情況下是有用的尖端:

請注意這種方法並不原子,它首先運行SELECT,和如果有任何結果的INSERT是嘗試。如果還有其他線程或進程,則兩個調用之間存在爭用條件,並且可能會出現兩個相似的記錄。

不管這是一個問題或不依賴於應用程序的邏輯,但在該行有一個唯一的約束的異常可以被升高的特定情況下,在執行一次:

begin 
    CreditAccount.find_or_create_by(user_id: user.id) 
rescue ActiveRecord::RecordNotUnique 
    retry 
end 

類似錯誤捕捉對於Rails 3可能會有用。(不知道Rails 3中是否會出現相同的ActiveRecord::RecordNotUnique錯誤,因此您的實現可能需要不同。)

+0

只是爲了記錄:Rails 3拋出一個'ActiveRecord :: StatementInvalid'(什麼不是很具體)。 – spickermann

+0

無賴。所以你仍然可能做一個'救援ActiveRecord :: StatementInvalid'爲了重試,即使它沒有像Rails 4那樣讀取? –

+0

你可以。但問題是'StatementInvalid'也包含無效的SQL,在非空列中包含NULL,依此類推。你可能會重試那些永遠不會工作的東西。也許你想使用像https://github.com/nfedyashev/retryable#readme這樣的東西來避免陷入循環。 – spickermann