并发导致重复记录插入

时间:2014-10-22 05:36:02

标签: mysql ruby-on-rails ruby database concurrency

对于我的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 :: Validations#save结合使用并不能保证不存在重复的记录插入,因为应用程序级别的唯一性检查本质上容易出现竞争条件。如果您使用具有“可序列化”隔离级别的事务,则甚至可能发生这种情况。

我检查了数据库隔离级别,它是“REPEATABLE-READ”,而不是“可序列化”。

SELECT @@global.tx_isolation;
REPEATABLE-READ

我可以添加复合唯一索引来解决这个问题。

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

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

1 个答案:

答案 0 :(得分:5)

根本原因可能是app中的竞争条件。 Rails validates_uniqueness_of并不能完全正常工作,因为当您遇到多线程,多个Web请求等竞争条件时,它无法保证数据库级别的唯一性。

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

在您的特定情况下,序列ID号可能是由,例如,用户双击&#34;保存&#34;触发两个Ajax请求(而不是一个)的按钮,以及执行模型find_or_create的Rails控制器。

REPEATABLE-READ的数据库隔离对于竞争条件并不重要。 REPEATABLE-READ表示在交易期间获得的每一把锁都是在交易期间保留的;你仍然可以进行幻读。 Rails教程试图解释的是,即使您正在使用SERIALIZABLE,也会发生validates_uniqueness_of竞争条件,这会阻止幻像读取,因此比REPEATABLE-READ更受保护。

要在您的应用中解决此问题,请停止仅依赖于Rails中的validates_uniqueness_of,并开始使用内置于数据库中的唯一性保证,例如唯一主键或您的唯一复合索引解决方案。这确保即使您在Rails中进行竞赛,您的数据库也会阻止竞争。

可以完成消除Rails(而不是数据库)中的竞争,但对于典型的Rails Web应用程序来说,它可能不是一种智能方法。例如,您可以通过使用一次只允许一个请求的Web服务器消除Rails中的竞争,并且一次只有一个Rails应用程序连接到数据库。

如果您发现您的比赛是由类似Ajax按钮的双击引起的,那么您可以通过在按钮中添加一些智能对您的用户非常有帮助,因此它不会两次发送相同的数据快速接班。这将消除许多常见用例的Ajax竞争。