Put pg_try_advisory_xact_lock() in a nested subquery?

后端 未结 1 478
一生所求
一生所求 2020-12-12 04:56

In my Ruby on Rails 4 app, I have this query to a Postgres 9.4 database:

@chosen_opportunity = Opportunity.find_by_sql(
  \" UPDATE \\\"opportunities\\\" s
          


        
相关标签:
1条回答
  • 2020-12-12 05:44

    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 UPDATE … LIMIT 1

    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
    • You don't need to return * from the subquery, just id is enough.

    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:

    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. 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. 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.

    0 讨论(0)
提交回复
热议问题