【问题标题】:Deal with PostgreSQL concurrency using Rails find_or_create使用 Rails find_or_create 处理 PostgreSQL 并发
【发布时间】:2014-05-22 09:24:54
【问题描述】:

由于某些原因,如果不同的用户同时运行这段代码,它可能会创建重复的游戏:

game = Game.find_or_create_by(
    status: Game::STATUS[:waiting],
    category_id: params[:category_id],
    private: 0
) do |g|
  is_new = true
  g.user = current_user
end

我不清楚是怎么回事,但可能是因为不同的 Unicorn 进程使用不同的数据库连接,因此事务可以并行运行。

如果是这样,我需要正确的方法来避免它,也许我应该使用 Rails 事务或 Postgres 锁,但我确实需要一个使用示例。

谢谢。

【问题讨论】:

  • 我相信您的问题出在 current_user 中,但首先将 find_or_create_by 更改为 create_by,因为 is_new 为 true。
  • 添加和index to the database 以避免重复条目?
  • manu29.d,实际上我不能在 [status, category_id, private] 上使用 UNIQUE 索引:因为我只需要状态为 0(WAITING)时的唯一记录。对于其他状态,我可以有记录,复制这些列。
  • Hitham S. AlQadheeb,我没明白你的意思。 current_user 有什么问题,create_by 是什么?以下代码中使用is_new,只是为了知道刚刚创建了记录。

标签: ruby-on-rails postgresql concurrency transactions


【解决方案1】:

可能发生在高并发级别。

根据rails docs,这些查询将运行:

SELECT * FROM games WHERE status = 'waiting' AND ... LIMIT 1;
INSERT INTO games (status, ...) VALUES ('waiting', ...);

第二个只运行,当第一个没有返回一行时。

如果两个(或更多)连接在几微秒内启动第一个查询,则多个进程可能会创建多个条目。为防止这种情况,您可以在该表上使用ACCESS EXCLUSIVE lock,或使用自定义advisory lock

您也可以使用一些唯一索引,以防止将多个条目插入到数据库中,但如果单独使用,则会在这种情况下导致 SQL 异常。

编辑

ACCESS EXCLUSIVE锁可以通过LOCK command获取。

咨询锁可以通过pg_advisory_lock(id) function获取。

两者都要求您运行任意 SQL 命令。


另一种方法是使用自定义查询:

  1. 仅在不存在时插入(并返回所有字段)
  2. 仅在未插入时选择

某事,例如:

INSERT INTO games (status, category_id, private)
     SELECT 'waiting', 2, 0
      WHERE NOT EXISTS (
             SELECT 1
               FROM games
              WHERE status = 'waiting'
                AND category_id = 2
                AND private = 0
       )
  RETURNING *;

-- only select, when this not inserted anything

SELECT *
  FROM games
 WHERE status = 'waiting'
   AND category_id = 2
   AND private = 0

【讨论】:

  • 谢谢。您能否提供一个关于如何在我的 Rails 代码中实现这些类型的锁的小例子?在我的情况下,rails 交易不会有用吗?
猜你喜欢
  • 2013-04-02
  • 2021-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-15
  • 1970-01-01
  • 2012-05-05
  • 1970-01-01
相关资源
最近更新 更多