ActiveRecord find_or_initialize_by竞争条件

时间:2014-10-08 05:45:22

标签: postgresql activerecord

我有一个场景,其中2个db连接可能都运行Model.find_or_initialize_by(params)并引发错误: PG :: UniqueViolation:ERROR:重复键值违反唯一约束

我想更新我的代码,以便它可以从中优雅地恢复。类似的东西:

record = nil

begin
  record = Model.find_or_initialize_by(params)
rescue ActiveRecord::RecordNotUnique
  record = Model.where(params).first
end

return record

麻烦的是,在我的本地计算机上重现这一点并不是一个好的/简单的方法,所以我不确定我的修复程序是否真的有效。

所以我觉得我有点创造性并尝试连续调用2次(本地)连续创建 PG :: UniqueViolation:ERROR 然后我可以从它确保一切都得到妥善处理。

但是我得到了这个错误: PG :: InFailedSqlTransaction:错误:当前事务被中止,命令被忽略直到事务块结束

即使我将所有内容都包装在各个事务块中,我也会收到此错误

record = nil

Model.transaction do
  record = Model.create(params)
end

begin
  Model.transaction do
    record = Model.create(params)
  end
rescue ActiveRecord::RecordNotUnique
end

Model.transaction do
  record = Model.where(params).first
end

return record

我的问题:

  • 优雅处理我在本文开头提到的竞争条件的正确方法是什么?
  • 如何在本地测试?

我想我可能会在这里找到一些简单的东西,但现在已经很晚了,也许我的想法并不太清楚。

我正在运行postgres 9.3和rails 4。

编辑结果发现find_or_initialize_by应该是find_or_create_by,我得到的错误来自稍后执行的实际保存调用。 #VeryTiredWhenIWroteThis

1 个答案:

答案 0 :(得分:4)

这实际发生了吗?

Model.find_or_initialize_by(params)

不应该引发'ActiveRecord :: RecordNotUnique'错误,因为它没有向db保存任何内容。它只是创建一个新的ActiveRecord。

然而,在第二个片段中,您正在创建记录。 create(没有爆炸)不会抛出验证引起的异常,但是 如果createcreate!

重复,则始终抛出ActiveRecord :: RecordNotUnique

如果您正在创建记录,则根本不需要交易。由于Postgres符合ACID,因此只保证两个操作中的一个成功,如果它响应,那么它的更改将是持久的。 (针对postgres的单个语句查询也是一个事务)。因此,如果您通过find_or_create_by

替换,那么您的上述代码几乎没有问题
begin
 record = Model.find_or_create_by(params)
rescue ActiveRecord::RecordNotUnique
 record = Model.where(params).first
end

您可以通过简单地尝试在行中创建两次相同的记录来测试代码是否正常运行。但是,这不会测试ActiveRecord::RecordNotUnique实际上是否在竞争条件下正确抛出。

您的应用程序也不负责测试和测试它并不容易。您必须在计算机上以多线程模式启动rails,或者针对多进程暂存rails实例进行测试。例如,Webrick一次只处理一个请求。你可以使用puma应用服务器,但是在MRI上没有真正的并发(GIL)。线程仅在IO阻塞时才共享GIL。因为与Postgres交谈是IO,我期待一些并发请求,但要100%肯定,最好的测试场景是部署在有多个工作人员的乘客上,然后使用jmeter再次运行并发请求服务器。