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建议的解决方法:
-
在属性上添加索引
add_index(:tickets, :ticket_number, :unique => true)
-
调整
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应该做同样的事情