Rails验证并发输入的唯一性失败

时间:2014-04-02 14:08:44

标签: mysql ruby-on-rails

我有一个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多张门票中只发生过一次,但为什么以及如何防止这种情况发生呢?

2 个答案:

答案 0 :(得分:2)

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

阅读: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记录

这一切都必须在几毫秒内完成,但技术上可行。

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

答案 1 :(得分:1)

@Yule的答案非常有用,它解释了为什么会发生这种情况,并且添加数据库级别约束会阻止它。

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

  1. 在属性上添加索引

    add_index(:tickets, :ticket_number, :unique => true)

  2. 调整controller#create操作以捕获数据库异常

  3. 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
    

    应该对控制器#review action ofcourse

    做同样的事情