2014-10-22 120 views
2

對於我的Rails應用程序,我發現mysql數據庫表的重複記錄。併發導致重複記錄插入

user_product表

ID | user_id | product_id |數額| created_at | update_at

181115 | 811115 | 1800 | 2 | 2014-10-16 03:00:13 | 2014-10-16 03:03:58

181116 | 811115 | 1800 | 2 | 2014-10-16 03:00:13 | 2014-10-16 03:03:58

但我在user_product模型中添加了validates_uniqueness_of

class UserProduct < ActiveRecord::Base 
    validates_uniqueness_of :product_id, scope: :user_id 
...... 
end 

我從rails doc找到了一些有用的信息。

在使用用ActiveRecord ::驗證#結合這個驗證方法節省並不能保證沒有重複記錄插入的,因爲在應用程序級別唯一性檢查本身容易出現競爭情況。如果您使用具有「可序列化」隔離級別的事務,則甚至可能發生這種情況。

我檢查了數據庫隔離級別,它是「REPEATABLE-READ」,而不是「可序列化」。

SELECT @@global.tx_isolation; 
REPEATABLE-READ 

我可以添加複合唯一索引來解決這個問題。

add_index :user_product, [:product_id, :user_id], unique: true 

但我希望找到根本原因,任何人都可以幫助我。非常感謝!

+0

是否ID不是主鍵? – 2014-10-22 05:50:10

+0

是的,它不是主鍵 – pangpang 2014-10-22 05:58:32

回答

4

根本原因可能是應用程序中的競爭條件。 Rails的validates_uniqueness_of並沒有真正完全的工作,因爲它不能保證在數據庫級別的唯一性,當你有競賽條件,如多線程,多個Web請求等

http://1rad.wordpress.com/2008/09/29/0x04-atomic-science/

在您的特定情況下,連續的id號可能是由例如用戶雙擊觸發兩個Ajax請求(而不是一個)的「保存」按鈕以及執行模型find_or_create的Rails控制器引起的。

您的數據庫隔離REPEATABLE-READ對競爭條件無關緊要。可重複閱讀意味着交易期間獲得的每個鎖都在交易期間持有;你仍然可以幻影讀取。 Rails教程試圖解釋的是,即使使用SERIALIZABLE,也會發生validates_uniqueness_of競態條件,這會阻止幻像讀取,因此比REPEATABLE-READ更受保護。

要在您的應用中修復此問題,請停止僅依靠Rails中的validates_uniqueness_of,並開始使用內置於數據庫中的唯一性保證,例如唯一主鍵或唯一組合索引的解決方案。這確保即使您在Rails中進行比賽,您的數據庫也會阻止比賽。

消除Rails(而不是數據庫)中的競爭可以完成,但對於典型的Rails Web應用程序來說,這可能不是一種明智的方法。例如,您可以通過使用一次只允許一個請求的Web服務器來消除Rails中的競爭,並且一次只有一個Rails應用程序連接到數據庫。

如果您發現您的種族是由類似於Ajax按鈕的東西雙擊引起的,您可以通過在按鈕中添加一些智能對您的用戶非常有用,因此它不會快速發送相同的數據兩次演替。這將消除許多常見用例的Ajax競爭。

+0

太棒了!我有一個問題:有什麼辦法來防止競賽狀況? – pangpang 2014-10-22 06:37:05

+0

是的。我添加了更多信息。 – joelparkerhenderson 2014-10-22 06:56:17

+0

很好!謝謝你的幫助! – pangpang 2014-10-22 07:23:25