lock not available
A statement using NOWAIT (or lock_timeout = 0) attempted to acquire a lock that was already held by another session. Rather than waiting, Postgres immediately raises this error so the caller can decide how to proceed.
- 1SELECT ... FOR UPDATE NOWAIT on a row locked by another transaction
- 2LOCK TABLE ... NOWAIT when another session holds a conflicting lock on the table
- 3ALTER TABLE with lock_timeout set to a very short value and a conflicting lock is present
- 4Advisory lock acquisition with pg_try_advisory_lock returning false (different code path but same pattern)
A NOWAIT lock attempt fails because another session holds the row lock.
CREATE TABLE items (id INT PRIMARY KEY, status TEXT); INSERT INTO items VALUES (1, 'pending'); -- Session 1 (holds lock): BEGIN; SELECT * FROM items WHERE id = 1 FOR UPDATE; -- Session 2 (immediate failure): SELECT * FROM items WHERE id = 1 FOR UPDATE NOWAIT; -- triggers 55P03
expected output
ERROR: could not obtain lock on row in relation "items"
Fix 1
Use SKIP LOCKED to skip contended rows
WHEN When processing a queue where any available row is acceptable (job queue pattern).
SELECT * FROM items WHERE status = 'pending' ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
Why this works
SKIP LOCKED causes the executor to skip any row whose lock cannot be immediately acquired, rather than waiting or failing. This is the standard pattern for Postgres-backed job queues: each worker atomically claims a different row without contention.
Fix 2
Remove NOWAIT and accept blocking (with lock_timeout)
WHEN When the lock will be released quickly and a brief wait is acceptable.
SET lock_timeout = '5s'; -- fail after 5 seconds instead of immediately SELECT * FROM items WHERE id = 1 FOR UPDATE;
Why this works
Without NOWAIT, the locking statement blocks until the conflicting lock is released or lock_timeout expires. lock_timeout raises 55P03 after the specified duration, providing a bounded wait without requiring application-level retry logic.
✕ Busy-loop retrying NOWAIT in a tight loop
Generates excessive load on the lock manager; use SKIP LOCKED or a blocking lock with lock_timeout instead.
SKIP LOCKED introduced. Earlier versions required NOWAIT with application-level retry.
Content generated with AI assistance and reviewed for accuracy. Found an error? hello@errcodes.dev