2015-10-14 66 views
4

在我的Rails应用4,我有这样的查询到一个Postgres 9.4数据库:咨询锁或NOWAIT以避免等待锁定的行?

@chosen_opportunity = Opportunity.find_by_sql(
    " UPDATE \"opportunities\" s 
    SET opportunity_available = false 
    FROM (
      SELECT \"opportunities\".* 
      FROM \"opportunities\" 
      WHERE (deal_id = #{@deal.id} 
      AND opportunity_available = true 
      AND pg_try_advisory_xact_lock(id)) 
      LIMIT 1 
      FOR UPDATE 
     ) sub 
    WHERE  s.id = sub.id 
    RETURNING sub.prize_id, sub.id" 
) 

很受this related answer on dba.SE启发。

我只是希望我的查询来查找和更新的第一个(随机,与LIMIT)排在那里available = true和其更新为available = false,我需要锁定该行,而这样做,但没有使等待发行的新要求因为有很多并发呼叫将使用此查询。我也看到NOWAIT选项FOR UPDATE。我不知道我的理解使用pg_try_advisory_xact_lock()NOWAIT选项之间的差别,他们似乎对我达到同样的目标:

  • NOWAIT's goal

    要防止等待其他操作要提交的事务,请使用NOWAIT选项。

  • pg_try_advisory_xact_lock's goal
    不是等待一个事务解除锁定,仍然能够做其他事务,并只经营则下一个更新选择“尚未锁定”行。

哪一个更适合我的需要?

回答

4

FOR UPDATE NOWAIT 仅仅是一个好主意,如果你坚持要锁定特定行,这是你所需要的。你只想任何排位赛,可用(解锁)行。最重要的区别是(quoting the manual for Postgres 9.4):

随着NOWAIT,该语句报告错误,而不是等待,如果选择的行不能立即锁定。

相同的查询很可能会尝试锁定相同的任意选择。 FOR UPDATE NOWAIT只会出现一个异常(除非您捕获错误,否则它将回滚整个事务)并且必须重试。

在dba.SE我的参考答案该解决方案采用的简单的FOR UPDATE结合pg_try_advisory_lock()组合:

pg_try_advisory_lock类似于为P g_advisory_lock,除了 功能不会等待锁成为可用。它会 立即获得锁并返回true,或者如果 无法立即获取锁,则返回false。

所以你最好的选择是......第三个选择:在Postgres的9.5(目前是测试版),它实现了无需额外的函数调用相同的行为,新FOR UPDATE SKIP LOCKED

The manual for Postgres 9.5比较这两个选项,解释差异多一些:

要防止等待其他事务 提交,使用NOWAITSKIP LOCKED选项操作。使用NOWAIT时,如果所选行 不能立即锁定,则 语句会报告错误而不是等待。通过SKIP LOCKED,可以跳过 无法立即锁定的任何选定行。

,同时等待新版本,你下一个最好的选择是结合使用pg_try_advisory_xact_lock(id)FOR UPDATE像展示在参考答案:

(也执行FOR UPDATE SKIP LOCKED。)

旁白

严格来说,你会得到任意的,而不是真正的随机选择。这可能是一个重要的区别。
查询的审计版本是my answer to your other question

+0

非常感谢您的详细解答。我会尽力实施下一个星期一。是的,你是正确的更新跳过锁定是最合适的。我可能会等待它的发布,并在此期间使用pg顾问锁定。因为我的排在这个阶段已经随机分布了,所以如果我没有得到任意选择,这并不重要。 – Mathieu

+0

@Mathieu:“任意”也很重要,因为它意味着Postgres通常会为相同的查询*任意*选择*相同的*行,这使得锁争论比真正的随机选择更大。 –

+0

你是什么意思'它会一直选择同一行'?如果你愿意,我的应用程序是一种彩票。当玩家检查'机票=机会'时,我会将其更新为available = false当玩家的下一张照片出现时,它会使用我们关注的查询并选择任何行,其中opportunity_available =真正。所以如果查询总是选择同一行,因为一旦玩家打开它就会被更新为不可用,我不认为这个任意的方面会影响我。你做? postgresql菜鸟 – Mathieu