mirror of
https://github.com/postgres/postgres.git
synced 2025-08-31 17:02:12 +03:00
Avoid spurious deadlocks when upgrading a tuple lock
When two (or more) transactions are waiting for transaction T1 to release a tuple-level lock, and transaction T1 upgrades its lock to a higher level, a spurious deadlock can be reported among the waiting transactions when T1 finishes. The simplest example case seems to be: T1: select id from job where name = 'a' for key share; Y: select id from job where name = 'a' for update; -- starts waiting for X Z: select id from job where name = 'a' for key share; T1: update job set name = 'b' where id = 1; Z: update job set name = 'c' where id = 1; -- starts waiting for X T1: rollback; At this point, transaction Y is rolled back on account of a deadlock: Y holds the heavyweight tuple lock and is waiting for the Xmax to be released, while Z holds part of the multixact and tries to acquire the heavyweight lock (per protocol) and goes to sleep; once X releases its part of the multixact, Z is awakened only to be put back to sleep on the heavyweight lock that Y is holding while sleeping. Kaboom. This can be avoided by having Z skip the heavyweight lock acquisition. As far as I can see, the biggest downside is that if there are multiple Z transactions, the order in which they resume after X finishes is not guaranteed. Backpatch to 9.6. The patch applies cleanly on 9.5, but the new tests don't work there (because isolationtester is not smart enough), so I'm not going to risk it. Author: Oleksii Kliukin Discussion: https://postgr.es/m/B9C9D7CD-EB94-4635-91B6-E558ACEC0EC3@hintbits.com
This commit is contained in:
@@ -36,6 +36,16 @@ do LockTuple as well, if there is any conflict, to ensure that they don't
|
||||
starve out waiting exclusive-lockers. However, if there is not any active
|
||||
conflict for a tuple, we don't incur any extra overhead.
|
||||
|
||||
We make an exception to the above rule for those lockers that already hold
|
||||
some lock on a tuple and attempt to acquire a stronger one on it. In that
|
||||
case, we skip the LockTuple() call even when there are conflicts, provided
|
||||
that the target tuple is being locked, updated or deleted by multiple sessions
|
||||
concurrently. Failing to skip the lock would risk a deadlock, e.g., between a
|
||||
session that was first to record its weaker lock in the tuple header and would
|
||||
be waiting on the LockTuple() call to upgrade to the stronger lock level, and
|
||||
another session that has already done LockTuple() and is waiting for the first
|
||||
session transaction to release its tuple header-level lock.
|
||||
|
||||
We provide four levels of tuple locking strength: SELECT FOR UPDATE obtains an
|
||||
exclusive lock which prevents any kind of modification of the tuple. This is
|
||||
the lock level that is implicitly taken by DELETE operations, and also by
|
||||
|
Reference in New Issue
Block a user