2014-10-22 127 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