I did test this inside a serializable transaction and it seems to work as expected. But I'm wondering about the guarantees here. What is the order of execution? My expectation is order/skip->limit->lock.
Would it ever lock more rows than one?
My actual scenario:
I have a table where each row represent a unit of work. I have multiple concurrent workers that should start a transaction, reserve one unit of work by select ... where completed=false order by random() for update skip locked limit 1 , and at the end of work update the row including a completed=true column before committing the transaction. If the reservation returns zero rows or errors with SQL Error [40001]: ERROR: could not serialize access due to concurrent update then it will restart its transaction. The point is to avoid doing unnecessary work by failing immediately at the beginning of work.
My concern is whether there could be dangling locks even if it selects only one row?
In the documentation for advisory locks, it warns against LIMIT queries:
because the LIMIT is not guaranteed to be applied before the locking function is executed. This might cause some locks to be acquired that the application was not expecting, and hence would fail to release (until it ends the session). From the point of view of the application, such locks would be dangling
However does this apply only to advisory locks and not SELECT FOR UPDATE? I cannot find such a caution documentation for SELECT FOR UPDATE. There is only a caution on the select docs about returning rows out of order, which is not a concern in my case because I just order by random anyway.
Yes, that is specific for advisory locks, which are taken using a function.
To answer questions like that, look at the execution plan. It will look similar to this:
The rows are locked after the sort, in the order they are returned by the sort, and processing will stop as soon as
LockRowshas locked the first row and passed it to theLimitnode.Note that
FOR UPDATEis only the correct lock if you intend to delete the row or modify a primary or unique key. For normalUPDATEs, you should useFOR NO KEY UPDATEto avoid excessive locking.You seem to be using an isolation level higher than read committed, because you are getting serialization errors. Note that that isn't required for this algorithm to be correct (but it is of course OK).