如何使ActiveRecord线程安全

5
如何在Rails 4和PostgreSQL中使以下控制器线程安全:
def controller_action
  if Model.exists(column_name:"some_value")
  else
    @model=Model.new(column_name:"some_value")
    @model.save
  end
end

我正在使用Puma,所以我的担忧是,如果两个线程同时运行这个控制器,并且不存在具有column_name指定值的行,则会创建两个记录,而我只想要1个。


Postgres会锁定事务,因此第一个事务将在数据库层阻止第二个事务。这通常是处理这种竞态条件的方式。 - TheIrishGuy
你的问题实际上与线程无关,而是一个普通的竞态条件。解决方案是在数据库中添加约束,并处理不可避免的异常(即将逻辑放在应该属于的数据库中)。 - mu is too short
所以让我确认一下我的理解...一个线程赢得了竞争条件并首先执行Model.exists; 同时,另一个线程被锁定。在@model.save之后,postgresql会删除锁定,另一个线程会看到现在存在一个具有所需值的行。如果是这样工作的,那么postgresql如何知道何时解除锁定?它是否总是等待控制器中的所有逻辑完成才解除锁定? - kempchee
5
@TheIrishGuy 无稽之谈。并发更新需要锁定,而并发插入不会相互阻塞。请参考 https://dev59.com/1mQm5IYBdhLWcg3w4CIN。 - Craig Ringer
2个回答

14

与评论相反,PostgreSQL中同一表上的并发插入是完全允许的,因此存在竞争条件。

为了保证安全性,您必须在column_name上有一个unique约束(或primary key)。重复插入将抛出异常,您可以捕获该异常并使用update进行重试。

如果没有unique约束,则必须使用LOCK TABLE ... IN EXCLUSIVE MODE来防止并发upserts。或者使用以下其中一种并发安全方法:

如何在PostgreSQL中实现UPSERT(MERGE、INSERT…ON DUPLICATE UPDATE)?


0
假设线程与连接是1:1的关系。因此,如果您有100个需要访问数据库的线程,您应该将连接池增加到100个。如果您的工作线程多于连接数,则需要使用队列(或其他线程安全数据结构)来管理它们之间的通信。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接