【问题标题】:Concurrent cause duplicate records inserted并发导致插入重复记录
【发布时间】:2014-10-22 05:36:02
【问题描述】:

对于我的 rails 应用程序,我从 mysql 数据库表中找到了重复记录。

user_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”,而不是“serializable”。

SELECT @@global.tx_isolation;
REPEATABLE-READ

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

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

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

【问题讨论】:

  • ID不是主键吗?
  • 是的,它不是主键

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


【解决方案1】:

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

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 更受保护。

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

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

如果你发现你的比赛是由双击 Ajax 按钮之类的东西引起的,你可以通过在按钮中添加一些智能来帮助你的用户,这样它就不会快速连续两次发送相同的数据.这将消除许多常见用例的 Ajax 竞争。

【讨论】:

  • 太棒了!我有一个问题:有什么办法可以防止比赛条件发生?
  • 是的。我添加了更多信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-15
  • 2018-08-17
相关资源
最近更新 更多