Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Postgres pg_try_advisory_lock blocks all records

I'm using pg_try_advisory_lock() in Postgres.

Next two queries lock more than one records in table1:

1)

SELECT a.id
FROM table1 a
JOIN table2 b ON a.table1_id = b.id
WHERE
    table2.id = 1
    AND
    pg_try_advisory_lock('table1'::regclass::integer, a.id)
LIMIT 1;

but

SELECT a.id
FROM table1 a
JOIN table2 b ON a.table1_id = b.id
WHERE table2.id = 1

returns one record.

2)

SELECT a.id
FROM table1 a
JOIN table2 b ON a.table1_id = b.id
JOIN table3 c ON b.table2_id = c.id
WHERE
    table3.id = 1
    AND
    pg_try_advisory_lock('table1'::regclass::integer, a.id)
LIMIT 1;

But I need pg_try_advisory_lock() to lock only one record.

What's wrong?

UPD

But the strange thing is that when I run the following query

SELECT a.id
FROM table1 a
JOIN table2 b ON a.table1_id = b.id
WHERE
    pg_try_advisory_lock('table1'::regclass::integer, a.id)
LIMIT 1;

Postgres locks only one row. So, Postgres scans the very first row then stops? I don't get it: it should scan all rows then limit the results to one row, or not?

like image 627
fdoo4un Avatar asked Nov 21 '13 08:11

fdoo4un


People also ask

How do I unlock my advisory lock?

A transactional advisory lock acquired in a transaction will be released when the transaction ends. In the previous section, we have acquired session level locks. To acquire a transaction level advisory lock, an alternative transaction specific function needs to be invoked.

What is Pg_try_advisory_lock?

pg_try_advisory_lock is similar to pg_advisory_lock , except the function will not wait for the lock to become available. It will either obtain the lock immediately and return true, or return false if the lock cannot be acquired immediately.

Does Postgres lock table on INSERT?

This means that an INSERT can safely write to a table without locking it, as any concurrent queries will simply ignore the new rows until the inserting transaction decides to commit. Thank you!

Does update lock table Postgres?

Mostly what happens when you try to UPDATE is that Postgres will acquire a lock on the row that you want to change. If you have two update statements running at the same time on the same row, then the second must wait for the first to process.


1 Answers

You're calling pg_try_advisory_lock() once per row in the entire set that gets scanned (as part of the filtering that occurs in the where clause), whereas you only want it called once per row in table1 returned by the query.

You could try using a subquery or a CTE instead:

with rows as (
SELECT a.id
FROM table1 a
JOIN table2 b ON a.table1_id = b.id
WHERE table2.id = 1
)
select rows.*
from rows
where pg_try_advisory_lock('table1'::regclass::integer, rows.id);

But don't rely on that to necessarily work as expected either: Postgres should be tempted to rewrite it the way your initial query was.

Another possibility is this, since the select part of a statement is evaluated very late in the query:

with rows as (
SELECT a.id,
       pg_try_advisory_lock('table1'::regclass::integer, a.id) as locked
FROM table1 a
JOIN table2 b ON a.table1_id = b.id
WHERE table2.id = 1
)
select rows.id
from rows
where rows.locked;

The real issue in practice is that pg_try_advisory_lock() is something you'd normally find in app land or in a function, rather than in a query like you're doing. Speaking of which, depending on what you're doing, are you sure you shouldn't be using select … for update?


Regarding your update:

postgres scans the very first row then stops?

Yes. Due to the limit 1, it's going to find a match and immediately stop. What is probably happening, though, is that it's not evaluating the where clause in the same order depending on your queries. SQL offers no guarantee that the a <> 0 part in a <> 0 and b / a > c gets evaluated first. Applied to your case, it offers no guarantee that the advisory lock is obtained after the row from a is joined with b.

like image 67
Denis de Bernardy Avatar answered Nov 03 '22 03:11

Denis de Bernardy