select open change scope Open full search

PG.CENTER connects PostgreSQL documentation, reference, and ecosystem knowledge. Maintained by Pigsty.

SQLSTATE / CLASS 55 · OBJECT NOT IN PREREQUISITE STATE

lock_not_available

SQLSTATE
55P03
Condition name
lock_not_available
Class
Object Not In Prerequisite State
Source macro
ERRCODE_LOCK_NOT_AVAILABLE
Evidence
Observed at runtime in the source evidence
Analysis & operational context

English SQLSTATE atlas: authored explanations and source/runtime evidence are separate from the manual definitions. View source ↗

<h1>55P03</h1>

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.

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.

-- setup (one maintenance session)
CREATE TABLE nowait_rows(id integer PRIMARY KEY, marker text NOT NULL);
INSERT INTO nowait_rows VALUES (1, 'seed');

-- blocker session: keep this transaction open
BEGIN;
SELECT id FROM nowait_rows WHERE id = 1 FOR UPDATE;

-- contender session, autocommit on: this is the 55P03 operation
SELECT id FROM nowait_rows WHERE id = 1 FOR UPDATE NOWAIT;

-- blocker session
ROLLBACK;

-- contender session after the blocker is released
UPDATE nowait_rows SET marker = 'repaired' WHERE id = 1 RETURNING marker;
SELECT marker FROM nowait_rows WHERE id = 1;

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.

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.

Source evidence

Evidence belongs to the frozen source and runtime versions listed here. It is not a runtime verification of the selected manual version.

55P03 is lock_not_available in SQLSTATE Class 55.

Method: Read fixed definition.

src.errcodes.18.6

The fixed row-lock, relation-lock, LOCK TABLE, lock_timeout, and maintenance paths support the concrete mechanisms and messages described.

Method: Read complete fixed source contexts.

Limits: Source confirmation is not natural runtime.

src.path.0 src.path.1 src.path.2 src.path.3 src.path.4

VACUUM/ANALYZE emits this SQLSTATE at WARNING for normal maintenance or LOG for verbose autovacuum, while lock and timeout paths use ERROR.

Method: Read the selected elevel branches.

src.path.4 src.path.2

The locked catalogue records 55P03 from 8.0.0; that boundary does not prove exact implementation introduction.

Method: Use catalogue boundary.

src.errcodes.18.6

The nowait_row_lock case passed on PostgreSQL 18.6 and 10.21 with SQLSTATE, severity or disconnect outcome, recovery/repair, cleanup, and isolated-target stop assertions.

Method: Execute the shared registry and inspect the selected summaries.

Limits: This is one bounded mechanism; it does not establish every source branch or business outcome.

runtime.ops-55p03-latest-1.latest runtime.ops-55p03-pg10-1.pg10 manifest.55P03 snippet-registry.55P03

Message templates

ERROR · message.0

Primary

could not obtain lock on row in relation "%s"
ERROR or DEBUG1 for skip-locked relation lookup · message.1

Primary

could not obtain lock on relation "%s.%s"
ERROR · message.2

Primary

canceling statement due to lock timeout
ERROR · message.3

Primary

could not obtain lock on relation "%s"
WARNING for ordinary maintenance or LOG for verbose autovacuum · message.4

Primary

skipping vacuum of "%s" --- lock not available
WARNING for ordinary maintenance or LOG for verbose autovacuum · message.5

Primary

skipping analyze of "%s" --- lock not available

Reproduction & repair cases

nowait_row_lock · PG 10, 18

Preconditions

  • Two runner-owned sessions target one row.
  • The blocker acquires its lock before the NOWAIT statement is sent.

Trigger: Hold a row lock in one explicit transaction, issue SELECT FOR UPDATE NOWAIT from a second session, then release and retry.

Expected assertions

  • SQLSTATE is 55P03 with ERROR severity
  • The diagnostic identifies a row lock that cannot be obtained immediately
  • The blocker is INTRANS and the NOWAIT contender remains IDLE after its error
  • After blocker rollback the same contender updates and reads the row

Repair: Use a bounded NOWAIT failure as a signal, release or resolve the blocker, then retry according to the application’s lock policy; this case does not treat sleeping as synchronization.

Cleanup: Close all runner connections and drop the case schema with an owner connection.

Recorded runtime evidence

18.6 (Homebrew) · passed

Run: ops-55p03-latest-1

{
  "table": "\"c55p03_nowait_row_lock\".\"nowait_rows\"",
  "statuses": {
    "blocker_after_lock": "INTRANS",
    "blocker_after_release": "IDLE",
    "contender_after_nowait": "IDLE",
    "contender_after_repair": "IDLE"
  },
  "diagnostic": {
    "text": "could not obtain lock on row in relation \"nowait_rows\"",
    "context": null,
    "severity": "ERROR",
    "sqlstate": "55P03",
    "table_name": null,
    "column_name": null,
    "schema_name": null,
    "source_file": "heapam.c",
    "source_line": "5235",
    "message_hint": null,
    "datatype_name": null,
    "exception_type": "LockNotAvailable",
    "internal_query": null,
    "message_detail": null,
    "constraint_name": null,
    "message_primary": "could not obtain lock on row in relation \"nowait_rows\"",
    "source_function": "heap_lock_tuple",
    "internal_position": null,
    "statement_position": null,
    "severity_nonlocalized": "ERROR"
  },
  "repair_marker": "repaired",
  "synchronization": "The blocker acquired SELECT FOR UPDATE before the contender issued SELECT FOR UPDATE NOWAIT; no timing sleep is used.",
  "connections_autocommit": {
    "blocker": true,
    "contender": true
  }
}
10.21 (Debian 10.21-1.pgdg90+1) · passed

Run: ops-55p03-pg10-1

{
  "table": "\"c55p03_nowait_row_lock\".\"nowait_rows\"",
  "statuses": {
    "blocker_after_lock": "INTRANS",
    "blocker_after_release": "IDLE",
    "contender_after_nowait": "IDLE",
    "contender_after_repair": "IDLE"
  },
  "diagnostic": {
    "text": "could not obtain lock on row in relation \"nowait_rows\"",
    "context": null,
    "severity": "ERROR",
    "sqlstate": "55P03",
    "table_name": null,
    "column_name": null,
    "schema_name": null,
    "source_file": "heapam.c",
    "source_line": "5074",
    "message_hint": null,
    "datatype_name": null,
    "exception_type": "LockNotAvailable",
    "internal_query": null,
    "message_detail": null,
    "constraint_name": null,
    "message_primary": "could not obtain lock on row in relation \"nowait_rows\"",
    "source_function": "heap_lock_tuple",
    "internal_position": null,
    "statement_position": null,
    "severity_nonlocalized": "ERROR"
  },
  "repair_marker": "repaired",
  "synchronization": "The blocker acquired SELECT FOR UPDATE before the contender issued SELECT FOR UPDATE NOWAIT; no timing sleep is used.",
  "connections_autocommit": {
    "blocker": true,
    "contender": true
  }
}

Definition snapshot: english-manuals:f660b1484c652b137bf8d6772f6… · English manual source