postgresql 9.4 - 阻止应用程序始终选择最新更新的行

时间:2015-10-25 14:41:15

标签: sql postgresql ruby-on-rails-4 concurrency

我有一个(Ruby on Rails 4)应用程序并使用postgresql查询基本上查看Opportunity表,在机会为'available = true'的行中随机搜索并使用'available = false更新这些选定的行”。 每次用户点击“尝试机会”时,该应用都会使用以下查询。

UPDATE opportunities s
SET    opportunity_available = false
FROM  (
   SELECT id
   FROM   opportunities
   WHERE  deal_id = #{@deal.id}
   AND    opportunity_available
   AND    pg_try_advisory_xact_lock(id)
   LIMIT  1
   FOR    UPDATE
   ) sub
WHERE     s.id = sub.id
RETURNING s.prize_id, s.id;
// inspired by https://stackoverflow.com/questions/33128531/put-pg-try-advisory-xact-lock-in-a-nested-subquery

我已经苦苦挣扎了5天但我现在已经成功地理解了它的表现(严重):现在我需要知道如何改变它。

确实,应用程序“变得有点疯狂”,并选择(并更新行)完全由我更新的最新版本。

让我明白我如何创造这些机会的过程

  • 我作为管理员通过我的应用程序作为管理员创建了20个机会(行)

  • 然后在我的管理面板中,我创建了一个奖品,数量让我们说奖品ID = 45,数量= 4.应用程序将进入商机表并随机填写(此部分完美运行)4行与prize_id = 45。

现在用户运行应用程序并且如上所述,应用程序将不会随机选择一行,但将始终以最新更新开始:它似乎带有奖品的一行,然后带奖品的另一行,然后另一个没有奖品的人(prized_id =空)......

现在我做了一些手动实验:我尝试手动更改表格上的值(通过pgadmin),最奇怪的事情发生了:如果我修改第32行,然后第45行,然后第67行,当用户再次尝试播放时,猜猜看,随机挑选的线条正好是我按相反顺序更新的线条:它将选择第67行然后是第45行,然后第32行。 它甚至不会考虑可以选择的其他行(所有其他可用的机会= true)。

我也尝试过不用于更新或'pg_try_advisory_xact_lock(id)'行,看起来它仍然有同样的问题。

作为一个管理员,我首先创建了总共20行,然后是4个获胜行,它们是最后一个被删除的行(即使在我的pgadmin屏幕上它们保持在同一行...也许在backround,postgresql将它们作为最后一次更新的顺序排序并选择它们?)这就是为什么只要选择了4个这些获胜行中的一个,然后所有其他行跟随。

要明确我可以通过逐行选择每个可用的机会(例如:第3行,然后第4行,然后按照我在pgadmin上看到的第5行,因为行已经完全归因于奖品)随机地)。问题是它没有这样做,它经常连续取得所有获胜的行....

我说不出话来,也不知道如何打破这种模式。

注意:这种模式不会在100%的情况下连续发生,但经常发生:例如,如果我有超过4个获胜行,那么如果我继续点击作为用户,它就像在这里说的那样,那就是2分钟,然后它停止并且似乎(或者我可能是错的)表现正常然后再次&分钟将再次只选择获胜的行...

编辑1

以下是如何在商机表中注入奖品(例如,我创建一个ID为21且奖品数量为3的奖品)=>它随机发送(据我所知)但只有在没有prize_id的情况下(即如果机会有prize_id =空,它可以把它放在那里)

  SQL (2.4ms)  
  UPDATE "opportunities"
  SET "prize_id" = 21
  WHERE "opportunities"."id" 
  IN (
    SELECT "opportunities"."id"
    FROM "opportunities"
    WHERE (deal_id = 341 AND prize_id IS NULL)
    ORDER BY RANDOM()
    LIMIT 3) //
   (0.9ms)  COMMIT

此SQL查询由Rails gem(名为Randumb:github.com/spilliton/randumb

生成

编辑2

我转到另一个更精确的问题:Postgresql 9.4 - FASTEST query to select and update on large dataset (>30M rows) with heavy writes/reads and locks

事实上,我认为问题实际上是我需要一个真正的 RANDOM 选择并远离 ARBITRARY 选择。

Erwin已经在Advisory locks or NOWAIT to avoid waiting for locked rows?说过了,但现在我明白了他的意思(“任意”也很重要,因为它暗示Postgresql通常会为同一个查询任意选择同一行,这会导致锁定争用比真正的随机选择更大的问题。“postgresql可以自由选择输出'LIMIT 1'的最快方式,并且它总是选择相同的行=那些已被提升的最后一行。但我无法做到这一点因为最新更新的所有获胜机会。

1 个答案:

答案 0 :(得分:2)

只是一个想法:而不是调用random()而不是使用它作为列的默认值(可以被索引)类似的方法可以使用增量约为0.7 * INT_MAX的串行。

\i tmp.sql

CREATE TABLE opportunities
    ( id SERIAL NOT NULL PRIMARY KEY
    , deal_id INTEGER NOT NULL DEFAULT 0
    , prize_id INTEGER
    , opportunity_available boolean NOT NULL DEFAULT False
            -- ----------------------------------------
            -- precomputed random() , (could be indexed)
    , magic DOUBLE precision NOT NULL default RANDOM()
    );

INSERT INTO opportunities(deal_id)
SELECT 341
FROM generate_series(1,20) gs
    ;
VACUUM ANALYZE opportunities;

PREPARE add_three (integer) AS (
WITH zzz AS (
  UPDATE opportunities
  SET prize_id = 21
    , opportunity_available = True
    -- updating magic is not *really* needed here ...
    , magic = random()
  WHERE opportunities.id
  IN (
    SELECT opportunities.id
    FROM opportunities
    WHERE (deal_id = $1 AND prize_id IS NULL)
    -- ORDER BY RANDOM()
    ORDER BY magic
    LIMIT 3)
RETURNING id, magic
    ) -- 
SELECT * FROM zzz
    );

PREPARE draw_one (integer) AS (
  WITH upd AS (
  UPDATE opportunities s
  SET    opportunity_available = false
  FROM  (
     SELECT id
     FROM   opportunities
     WHERE  deal_id = $1
     AND    opportunity_available
     AND    pg_try_advisory_xact_lock(id)
     ORDER BY magic
     LIMIT  1

     FOR    UPDATE
     ) sub
  WHERE     s.id = sub.id
  RETURNING s.prize_id, s.id, magic
    )
SELECT * FROM upd
    );

SELECT * FROM opportunities;

\echo add3
EXECUTE add_three(341);
SELECT * FROM opportunities;

\echo add3 more
EXECUTE add_three(341);
SELECT * FROM opportunities;

\echo draw1
EXECUTE draw_one(341);
SELECT * FROM opportunities;

\echo draw2
EXECUTE draw_one(341);
SELECT * FROM opportunities;

VACUUM ANALYZE opportunities;

\echo draw3
EXECUTE draw_one(341);
SELECT * FROM opportunities;

\echo draw4
EXECUTE draw_one(341);
SELECT * FROM opportunities;