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
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 .
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).
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 .
Should I place pg_try_advisory_lock()
outside the WHERE
clause? 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:
Let me simplify a few things in your query first:
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;
opportunity_available = true
to just opportunity_available
*
from the subquery, just id
is enough. Typically, this works as is . Explanation below.
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.
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.