Railsvalidation唯一性在并发输入上失败

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

 validates :ticket_number, uniqueness: true 

这是ticket_number的输入代码

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

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

到目前为止,它只发生过600多张票,但为什么以及如何防止这种情况发生呢?

这是非常罕见的,你可能非常不幸,它是可能的。

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

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

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

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

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

  1. 在属性上添加索引

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

  2. 调整controller#create动作以捕获dbexception

 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应该做同样的事情