pan*_*ang 2 ruby mysql database concurrency ruby-on-rails
对于我的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
Run Code Online (Sandbox Code Playgroud)
我从rails doc中找到了一些有用的信息。
将此验证方法与ActiveRecord :: Validations#save结合使用并不能保证没有重复的记录插入,因为在应用程序级别进行的唯一性检查本质上容易出现竞争状况。如果您使用具有“可序列化”隔离级别的事务,甚至可能发生这种情况。
我检查了数据库隔离级别?它是“ REPEATABLE-READ”?不可“序列化”。
SELECT @@global.tx_isolation;
REPEATABLE-READ
Run Code Online (Sandbox Code Playgroud)
我可以添加复合唯一索引来解决此问题。
add_index :user_product, [:product_id, :user_id], unique: true
Run Code Online (Sandbox Code Playgroud)
但我希望找到根本原因,任何人都可以帮助我。非常感谢!
根本原因可能是应用程序中的竞争状况。Rails validates_uniqueness_of并不能完全正常工作,因为当您遇到竞争条件(例如多个线程,多个Web请求等)时,Rails 无法保证数据库级别的唯一性。
http://1rad.wordpress.com/2008/09/29/0x04-atomic-science/
在您的特定情况下,顺序ID号可能是由于,例如,用户双击触发两个Ajax请求(而不是一个)的“保存”按钮以及执行模型的Rails控制器引起的find_or_create。
您的数据库REPEATABLE-READ的隔离与竞争条件无关紧要。REPEATABLE-READ表示在交易期间持有交易期间获得的每个锁;您仍然可以幻像读取。Rails教程试图解释的是,validates_uniqueness_of即使您使用SERIALIZABLE,竞争条件也会发生,这可以防止幻像读取,因此比REPEATABLE-READ更受保护。
要在您的应用程序中解决此问题,请不再完全依赖validates_uniqueness_ofRails,而开始使用数据库中内置的唯一性保证,例如唯一主键或唯一组合索引的解决方案。这样可以确保即使在Rails中进行比赛,数据库也将阻止比赛。
消除Rails(不是DB)的竞争是可以实现的,但是对于典型的Rails Web应用程序来说,这可能不是明智的选择。例如,您可以通过使用一次仅允许一个请求并且一次仅一个Rails应用程序连接到数据库的Web服务器来消除Rails中的竞争。
如果您发现自己的比赛是由双击Ajax按钮之类的原因引起的,则可以通过在按钮中添加一些智能功能来为用户提供帮助,这样它就不会连续两次发送相同的数据。这将消除许多常见用例的Ajax竞争。