2014-10-17 16 views
0

我正在深入研究Rails 4,並且試圖瞭解如何在多個數據庫連接訪問模型數據時安全地訪問模型數據。我有一些比賽制定邏輯找到最早用戶在隊列中,從隊列中刪除該用戶,並使用戶返回...Rails應用程序中的實例可以在檢查時由其他數據庫連接修改嗎?

# UserQueue.rb 
class UserQueue < ActiveRecord::Base 
    has_many :users 

    def match_user(user) 
    match = nil 
    if self.users.count > 0 
     oldest = self.users.oldest_in_queue 
     if oldest.id != user.id 
     self.users.delete(oldest) 
     match = oldest 
     end 
    end 
    end 
end 

如果兩個不同的線程中執行大約在同一時間該match_user方法,是否有可能找到相同的oldest用戶並嘗試從隊列中刪除它,並將其返回給調用者?如果是這樣,我該如何預防?

我看着交易,但他們似乎並沒有成爲一個解決方案,因爲有在此情況下(隊列)被修改只有一個型號的。

在此先感謝您的智慧!

回答

1

的ActiveRecord有行鎖定的支持。

這是從Rails guide, locking records for update採取:

11.1樂觀鎖

樂觀鎖允許多個用戶訪問用於編輯相同的記錄,並假定最小的與數據衝突。它通過檢查另一個進程是否自打開之後對記錄進行更改來完成此操作。如果發生並且更新被忽略,則會拋出一個ActiveRecord::StaleObjectError異常。

樂觀鎖柱

爲了使用樂觀鎖,該表需要有一個柱被稱爲整數類型lock_version。每次記錄更新時,「活動記錄」都會增加lock_version列。如果更新請求在lock_version字段中的值低於當前在數據庫中的lock_version列中的更低值,則更新請求將失敗並顯示ActiveRecord::StaleObjectError。例如:

c1 = Client.find(1) 
c2 = Client.find(1) 

c1.first_name = "Michael" 
c1.save 

c2.name = "should fail" 
c2.save # Raises an ActiveRecord::StaleObjectError 

你然後通過拯救異常,並處理衝突,要麼回滾,合併或以其他方式適用於解決衝突所需的業務邏輯負責。

此行爲可以通過設置ActiveRecord::Base.lock_optimistically = false來關閉。

要覆蓋lock_version列的名稱,ActiveRecord::Base提供了一個名爲locking_column類屬性:

class Client < ActiveRecord::Base 
    self.locking_column = :lock_client_column 
end 

我建議閱讀本section in the Rails guide

+1

謝謝,實質。如果我正確地理解了這一點,聽起來像我只需要將我的代碼包裝在開始/救援塊中,並添加處理隊列第二次修改的情況所需的業務邏輯?另外,在閱讀指南後,看起來我也可以使用「悲觀」鎖定,並將代碼包裝在交易中並呼叫鎖定!自我(隊列實例)?再次感謝! – BeachRunnerFred 2014-10-18 01:02:08

0

是的,那絕對會發生。你如何防止它取決於你的應用程序/框架/數據庫/等的其餘部分。

交易將沒有幫助,因爲兩個客戶端可以在同一時間開始的請求,都將看到同樣的UserQueue記錄作爲歷史最悠久的。

你想要一個mutex。但是,如果有其他方法來修改數據(例如,直接通過SQL等),代碼中的互斥體並不理想。它也會變得混亂,因爲第一次忘記使用互斥體時,您已經打開競賽狀況。然後,也許這就夠了。只要記住你的互斥量需要跨線程和進程的工作,等等。

您可能會看到,如果你的數據庫有一個互斥體或其他行級鎖,你可以使用標記的最古老的隊列記錄,然後將其解壓縮。

或者找到另一種方式來抓住這完全避免了競爭條件最古老的隊列。例如:

  • SQL更新最舊的隊列,其ID不是user.id,並將「marked_for_work」設置爲某個唯一ID。
  • 獲取其marked_for_work值是我們唯一ID的隊列行。

你可以用上面的方式運行多個線程,不用擔心,因爲SQL更新是(應該是!)原子的。

相關問題