55P03ERRORTier 2 — Caution✅ HIGH confidencelock not available
What this means
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.
Why it happens
- 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)
How to reproduce
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 55P03Fix 1: Use SKIP LOCKED to skip contended rows
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 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.
What not to do
Busy-loop retrying NOWAIT in a tight loop
Why it's wrong: Generates excessive load on the lock manager; use SKIP LOCKED or a blocking lock with lock_timeout instead.
Version notes
Postgres 9.5+SKIP LOCKED introduced. Earlier versions required NOWAIT with application-level retry.Sources
📚 Official docs: https://www.postgresql.org/docs/current/errcodes-appendix.html
📚 Feature docs: https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE
🔧 Source ref: src/backend/storage/lmgr/lock.c — LockAcquireExtended()
📖 Further reading: Explicit Locking
Confidence assessment
✅ HIGH confidence
Well-documented. NOWAIT and SKIP LOCKED behaviour are stable since their respective introduction versions. Edge case: lock_timeout applies to all lock acquisitions including relation-level locks from DDL, not just row locks.
See also
🔗 Related errors
📄 Reference pages