【问题标题】:Rails validate uniqueness fails on concurrent inputsRails 验证并发输入的唯一性失败
【发布时间】:2014-04-02 14:08:43
【问题描述】:

我有一个 Ticket 模型,对字符串属性 ticket_number 进行基本唯一性验证

validates :ticket_number, uniqueness: true

这是ticket_number的输入代码

<%= f.number_field :ticket_number, :value => @ticket.ticket_number || (Ticket.exists? ? Ticket.maximum(:ticket_number).next) : 1 , :class => 'form-control' %>

验证似乎按预期工作,但是当我们部署应用程序并且可能有多个用户同时输入 tickets 时,这应该不会导致问题(对吗?)我们发现重复记录(相同票号)在数据库(MySQL)中。

到目前为止,总共 600 多张票中只发生过一次,但为什么以及如何防止这种情况发生?

【问题讨论】:

    标签: mysql ruby-on-rails


    【解决方案1】:

    这是非常罕见的,你可能很不走运,有可能。

    阅读:https://github.com/rails/rails/blob/master/activerecord/lib/active_record/validations/uniqueness.rb#L165

    考虑以下几点: 用户 A 提交表单

    • 用户 A 提交表单
    • Rails 检查数据库中用户 A 的现有 ID - 未找到
    • 用户B提交表单
    • Rails 检查数据库中用户 B 的现有 ID - 未找到
    • Rails 保存用户 A 记录
    • Rails 保存用户 B 记录

    所有这一切都必须在几毫秒内发生,但在技术上是可行的。

    我建议在数据库级别(主键)添加一个约束。

    【讨论】:

    • 感谢尤尔的提示。我一直面临着类似的问题,但恐怕我无法在数据库级别放置唯一索引。我还有其他选择吗?谢谢
    【解决方案2】:

    @Yule 的回答非常有帮助,它解释了为什么会发生这种情况以及添加数据库级别的约束会阻止它。

    docs 说,这将抛出一个 ActiveRecord::RecordNotUnique exseption,我当然不希望用户看到,所以这正是如何应用 @Yule 建议的解决方法:

    1. 在属性上添加索引

      add_index(:tickets, :ticket_number, :unique =&gt; true)
    2. 调整 controller#create 操作以捕获 db 异常

    def create
     @ticket = Ticket.new(ticket_params)
     if @ticket.save
       flash[:success] = 'Ticket was successfully created.'
     else
       render action: 'new'
     end
     #catch the exception
     rescue ActiveRecord::RecordNotUnique
       flash[:danger] = 'The ticket number you entered is already taken !'
       render action: 'new'
    end
    

    controller#update action ofcourse 也应该这样做

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多