并发导致插入重复记录

问题描述 投票:0回答:1

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

用户_产品表

id 用户 ID 产品_id 金额 创建于 更新时间
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

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

mysql ruby-on-rails ruby database concurrency
1个回答
5
投票

根本原因可能是应用程序中的竞争条件。 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 教程试图解释的是,即使您使用 SERIALIZABLE,也会发生

validates_uniqueness_of
竞争条件,这可以防止幻读,因此比 REPEATABLE-READ 受到更多保护。

要在您的应用程序中解决此问题,请停止完全依赖 Rails 中的

validates_uniqueness_of
,并开始使用数据库中内置的唯一性保证,例如唯一主键或唯一复合索引的解决方案。这可以确保即使您在 Rails 中进行竞赛,您的数据库也会阻止竞赛。

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

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

© www.soinside.com 2019 - 2024. All rights reserved.