咨询锁或NOWAIT以避免等待锁定的行?

Mat*_*ieu 4 sql postgresql concurrency ruby-on-rails ruby-on-rails-4

在我的Rails 4应用程序中,我对Postgres 9.4数据库进行了此查询:

@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"
) 
Run Code Online (Sandbox Code Playgroud)

非常受到dba.SE相关答案的启发.

我只是希望我的查询找到并更新第一个(随机,有LIMIT)行,available = true并将其更新到available = false,我需要在执行此操作时锁定行,但不要让新请求等待释放上一个锁,因为有许多将使用此查询的并发调用.

但我也看到了NOWAIT选项FOR UPDATE.我不确定我理解使用pg_try_advisory_xact_lock()NOWAIT选项之间的区别,他们似乎在我看来实现了同样的目标:

哪一个更适合我的需要?

Erw*_*ter 7

FOR UPDATE NOWAIT如果你坚持要锁定一个特定的行,这是一个好主意,这不是你需要的.您只需要任何合格的,可用的(未锁定的)行.重要的区别在于(引用Postgres 9.4的手册):

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

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

我在dba.SE上引用的答案中的解决方案使用了plain FOR UPDATE 的组合和pg_try_advisory_lock():

pg_try_advisory_lock类似于pg_advisory_lock,除了函数不会等待锁变为可用.它将立即获得锁定并返回true,如果无法立即获取锁定,则返回false.

所以你最好的选择是......第三种选择:FOR UPDATE SKIP LOCKEDPostgres 9.5中的新选项,它实现相同的行为而无需额外的函数调用.

Postgres 9.5的手册比较了两个选项,更多地解释了这些差异:

要防止操作等待其他事务提交,请使用NOWAITSKIP LOCKED选项.随着NOWAIT,该语句报告错误,而不是等待,如果选择的行不能立即锁定.使用时SKIP LOCKED,将跳过任何无法立即锁定的选定行.

在Postgres 9.4或更早版本中,您的下一个最佳选择pg_try_advisory_xact_lock(id)FOR UPDATE参考答案中所示的结合使用:

(还有一个实现 FOR UPDATE SKIP LOCKED.)

在旁边

严格来说,你是随意的,而不是真正的随机选择.这可能是一个重要的区别.
您的查询的审核版本是我对您的其他问题的回答.