简体   繁体   English

将pg_try_advisory_xact_lock()放在嵌套子查询中?

[英]Put pg_try_advisory_xact_lock() in a nested subquery?

In my Ruby on Rails 4 app, I have this query to a Postgres 9.4 database: 在我的Ruby on 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"
)

Very much inspired by this related answer on dba.SE . dba.SE上的相关答案极大地启发了我们

But here ( Postgres pg_try_advisory_lock blocks all records ) they say, if I'm not mistaken, that I should not use pg_try_advisory_lock() inside the WHERE clause because I would be calling it once per row in the entire set that gets scanned (as part of the filtering that occurs in the where clause). 但是在这里( Postgres pg_try_advisory_lock阻止了所有记录 ),他们说,如果我没记错的话,我不应该在WHERE子句中使用pg_try_advisory_lock()因为我将在被扫描的整个集中的每一行调用一次它。在where子句中进行的过滤)。

I just want my query to find and update the first (randomly, with LIMIT ) row where available = true and update it to available = false , and I need to lock the row while doing this, but without making new requests waiting for the release of the previous lock so I added advisory locks like suggested here . 我只想让我的查询查找并更新第一行(使用LIMIT随机地),其中available = true并将其更新为available = false ,我需要在执行此操作时锁定该行,但无需等待新的请求来等待发布之前的锁中的一个,因此我添加了这里建议的咨询锁。

Should I place pg_try_advisory_lock() outside the WHERE clause? 我应该将pg_try_advisory_lock()放在WHERE子句之外吗? How to do it? 怎么做?

I updated my referenced answer with more explanation and links. 我更新了参考答案,并提供了更多解释和链接。
In Postgres 9.5 (currently beta) the new SKIP LOCKED is a superior solution: 在Postgres 9.5(当前为beta)中,新的“ SKIP LOCKED是一种出色的解决方案:


Let me simplify a few things in your query first: 让我先简化一下查询中的几件事:

Straight query 直接查询

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;
  • All the double quotes were just noise with your legal, lower-case names. 所有的双引号都带有合法的小写字母名称。
  • Since opportunity_available is a boolean column you can simplify opportunity_available = true to just opportunity_available 由于possible_available是一个布尔列,因此您可以将opportation_available opportunity_available = true简化为opportunity_available
  • You don't need to return * from the subquery, just id is enough. 您不需要从子查询中返回* ,只需id就足够了。

Typically, this works as is . 通常,这是按原样工作的 Explanation below. 以下说明。

Avoid advisory lock on unrelated rows 避免对不相关的行进行咨询性锁定

To be sure, you could encapsulate all predicates in a CTE or a subquery with the OFFSET 0 hack (less overhead) before you apply pg_try_advisory_xact_lock() in the next query level: 可以肯定的是,您可以在下一个查询级别应用pg_try_advisory_xact_lock() 之前 ,将所有谓词用OFFSET 0 hack(较少的开销)封装在CTE或子查询中:

UPDATE opportunities s
SET    opportunity_available = false
FROM (
   SELECT id
   FROM  ( 
      SELECT id
      FROM   opportunities
      WHERE  deal_id = #{@deal.id}
      AND    opportunity_available
      AND    pg_try_advisory_xact_lock(id)
      OFFSET 0
      ) sub1
   WHERE  pg_try_advisory_xact_lock(id)
   LIMIT  1
   FOR    UPDATE
   ) sub2
WHERE     s.id = sub.id
RETURNING s.prize_id, s.id;

However , this is typically much more expensive. 但是 ,这通常要贵​​得多。

You probably don't need this 你可能不需要这个

There aren't going to be any "collateral" advisory locks if you base your query on an index covering all predicates, like this partial index: 如果您将查询基于涵盖所有谓词的索引(例如此部分索引),则不会有任何“附带”咨询锁:

CREATE INDEX opportunities_deal_id ON opportunities (deal_id)
WHERE opportunity_available;

Check with EXPLAIN to verify Postgres actually uses the index. EXPLAIN一起检查以验证Postgres实际使用了索引。 This way, pg_try_advisory_xact_lock(id) will be a filter condition to the index or bitmap index scan and only qualifying rows are going to be tested (and locked) to begin with, so you can use the simple form without additional nesting. 这样, pg_try_advisory_xact_lock(id)将成为索引或位图索引扫描的过滤条件,并且只有合格的行才开始进行测试(并锁定),因此您可以使用简单的表单而无需其他嵌套。 At the same time, your query performance is optimized. 同时,您的查询性能得到了优化。 I would do that . 我会做到这一点

Even if a couple of unrelated rows should get an advisory lock once in a while, that typically just doesn't matter. 即使几个不相关的行偶尔会获得咨询锁,通常也没关系。 Advisory locks are only relevant to queries that actually use advisory locks. 咨询锁仅与实际使用咨询锁的查询有关。 Or do you really have other concurrent transactions that also use advisory locks and target other rows of the same table? 还是您真的有其他并发事务也使用咨询锁并定位同一表的其他行? Really? 真?

The only other problematic case would be if massive amounts of unrelated rows get advisory locks, which can only happen with a sequential scan and is very unlikely even then. 唯一有问题的情况是,如果大量无关的行都获得了咨询锁,这只能在顺序扫描中发生,即使在那时也不太可能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM