55P03 — lock_not_available
55P03
At a glance
55P03 reports that a requested lock could not be obtained under the operation’s selected wait policy. Fixed paths cover row locks, relation locks, LOCK TABLE, lock_timeout, and maintenance commands that skip work.
| Field | Value |
|---|---|
| SQLSTATE | 55P03 |
| Condition | lock_not_available |
| Status | active |
| Known present by | 8.0.0 |
| Locked snapshots | 9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3 |
| Macros | ERRCODE_LOCK_NOT_AVAILABLE |
| Aliases | — |
Meaning
Row-lock callers use LockWaitError and ConditionalXactLockTableWait()/ConditionalMultiXactIdWait() to fail immediately with could not obtain lock on row in relation "%s". Relation and LOCK TABLE callers use ConditionalLockRelationOid() and report a relation variant. A lock_timeout interrupt is another ERROR path, canceling statement due to lock timeout; it means the wait exceeded the configured timeout, not that NOWAIT was requested.
VACUUM and ANALYZE use the same SQLSTATE when they cannot acquire their relation lock, but their ereport(elevel) is deliberately non-ERROR: ordinary maintenance reports a WARNING, and an autovacuum verbose path can use LOG. The command skips that object and continues, so a warning is not an aborted transaction.
Diagnosis
Keep the complete primary message, severity, relation or row name, lock mode, and statement. Inspect pg_stat_activity, pg_locks, blocker PIDs, transaction age, and whether the statement used NOWAIT, lock_timeout, or a maintenance command. Do not turn a skipped VACUUM/ANALYZE warning into a failed DML diagnosis.
The accepted row-lock case used a separate blocker and an autocommit contender. In an explicit transaction, a lock ERROR leaves the session INERROR until ROLLBACK or ROLLBACK TO SAVEPOINT; the accepted autocommit contender remained IDLE after its failed statement.
Response
Coordinate or release the blocker, or choose a deliberate wait/timeout/skip policy. For an ERROR in an explicit transaction, roll back the whole transaction or to an existing savepoint before issuing more SQL; verify which work was committed before replaying. In autocommit, change the blocking or timeout condition first, then retry the statement with an idempotency rule. For a maintenance WARNING/LOG, record the skipped relation and run maintenance later; do not issue ROLLBACK for a command that did not abort the transaction.
Observed diagnostics
The selected registry created a blocker holding SELECT ... FOR UPDATE in an explicit transaction and an autocommit contender issuing FOR UPDATE NOWAIT. PostgreSQL 18.6 and 10.21 returned ERROR / 55P03 with could not obtain lock on row in relation "nowait_rows"; the contender stayed IDLE. After the blocker rolled back, the same contender updated marker to repaired and verified it.
Representative case
The SQL is a role-labelled registry excerpt. Setup is run once; blocker and contender must be separate sessions. It covers the row-lock NOWAIT branch only, not lock_timeout or the maintenance skip branches.
The passed case confirms this row-lock mechanism, its ERROR/IDLE boundary, and its repair step only; it does not prove that every lock mode or maintenance command has the same severity.
Versions
The locked catalogue records this condition from 8.0.0; fixed source coverage is PostgreSQL 18.6. The accepted runtime was observed on PostgreSQL 18.6 and 10.21.
Related
Sources
src/backend/access/heap/heapam.c#L5178-5210
src/backend/catalog/namespace.c#L585-610
src/backend/tcop/postgres.c#L3420-3444
src/backend/commands/lockcmds.c#L130-145
src/backend/commands/vacuum.c#L835-880
The structured evidence record records fixed message roles, maintenance severity, and accepted runtime artifacts.