# 55P03 — lock_not_available

> PostgreSQL SQLSTATE 55P03 的完整来源与诊断条目。
---

# 55P03

## 速览 {#at-a-glance}

`55P03` 表示在当前等待策略下无法取得请求的锁。固定路径覆盖行锁、关系锁、`LOCK TABLE`、`lock_timeout`，以及无法取得关系锁时跳过对象的维护命令。

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `55P03` |
| 条件名 | `lock_not_available` |
| 状态 | `有效` |
| 已知存在于 | `8.0.0` |
| 锁定快照 | `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` |
| 宏 | `ERRCODE_LOCK_NOT_AVAILABLE` |
| 别名 | `—` |

<!-- source facts: data/errcodes/55P03.json -->
<!-- END SQLSTATE FACTS -->

## 含义 {#meaning}

行锁调用使用 `LockWaitError` 和 `ConditionalXactLockTableWait()`/`ConditionalMultiXactIdWait()`，立即报告 `could not obtain lock on row in relation "%s"`。关系锁和 `LOCK TABLE` 使用 `ConditionalLockRelationOid()`，报告关系对象变体。`lock_timeout` 是另一个 ERROR 路径，报 `canceling statement due to lock timeout`；它表示等待超过配置的 timeout，并不表示请求使用了 `NOWAIT`。

VACUUM 和 ANALYZE 取得关系锁失败时也使用此 SQLSTATE，但它们的 `ereport(elevel)` 有意不提升为 ERROR：普通维护报告 WARNING，autovacuum 的 verbose 路径可能使用 LOG。命令会跳过该对象并继续，因此这类 warning 不等于事务已中止。

## 诊断 {#diagnosis}

保留完整 primary message、severity、关系或行名、锁模式和语句。检查 `pg_stat_activity`、`pg_locks`、阻塞 PID、事务时长，以及语句使用的是 `NOWAIT`、`lock_timeout` 还是维护命令。不要把 VACUUM/ANALYZE 跳过对象的 warning 当成 DML 失败。

已接受的行锁案例使用独立 blocker 和 autocommit contender。显式事务中的锁 ERROR 会使 session 进入 `INERROR`，直到 `ROLLBACK` 或 `ROLLBACK TO SAVEPOINT`；该案例的 autocommit contender 在失败后保持 `IDLE`。

## 处理 {#response}

协调或释放 blocker，或明确选择等待、timeout、skip 策略。显式事务中的 ERROR 后，先回滚整个事务或回到既有 savepoint，再发送 SQL，并核对已提交工作后重放。autocommit 下先改变阻塞或 timeout 条件，再按幂等规则重试语句。维护 WARNING/LOG 只需记录跳过的关系并稍后补做维护；命令没有中止事务时不要执行 `ROLLBACK`。

## 实测诊断 {#observed-diagnostics}

选定注册表创建 blocker，在显式事务中持有 `SELECT ... FOR UPDATE`，并由 autocommit contender 发出 `FOR UPDATE NOWAIT`。PostgreSQL 18.6 与 10.21 均返回 `ERROR` / `55P03` 及 `could not obtain lock on row in relation "nowait_rows"`；contender 保持 `IDLE`。blocker 回滚后，同一 contender 将 `marker` 更新为 `repaired` 并完成验证。

## 代表案例 {#case}

以下 SQL 是带角色说明的注册表片段。setup 只执行一次，blocker 与 contender 必须使用不同会话；片段只覆盖行锁 `NOWAIT`，不覆盖 `lock_timeout` 或维护跳过分支。

<!-- BEGIN SQLSTATE SNIPPET: nowait_row_lock -->
```sql
-- setup（一个维护会话）
CREATE TABLE nowait_rows(id integer PRIMARY KEY, marker text NOT NULL);
INSERT INTO nowait_rows VALUES (1, 'seed');

-- blocker 会话：保持事务打开
BEGIN;
SELECT id FROM nowait_rows WHERE id = 1 FOR UPDATE;

-- contender 会话，开启 autocommit：这里触发 55P03
SELECT id FROM nowait_rows WHERE id = 1 FOR UPDATE NOWAIT;

-- blocker 会话
ROLLBACK;

-- 释放 blocker 后，在 contender 会话中修复并验证
UPDATE nowait_rows SET marker = 'repaired' WHERE id = 1 RETURNING marker;
SELECT marker FROM nowait_rows WHERE id = 1;
```
<!-- END SQLSTATE SNIPPET -->

该案例只确认行锁机制、`ERROR`/`IDLE` 边界和修复步骤，不能据此断言每种锁模式或维护命令有相同 severity。

## 版本 {#versions}

锁定目录从 8.0.0 记录此条件；固定源码覆盖 PostgreSQL 18.6。已接受的运行观察来自 PostgreSQL 18.6 和 10.21。

## 相关 {#related}

[`55P02`](../55p02/)、[`40001`](../40001/)、[`57014`](../57014/)

## 来源 {#sources}

[src/backend/access/heap/heapam.c#L5178-5210](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/access/heap/heapam.c#L5178)

[src/backend/catalog/namespace.c#L585-610](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/catalog/namespace.c#L585)

[src/backend/tcop/postgres.c#L3420-3444](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/tcop/postgres.c#L3420)

[src/backend/commands/lockcmds.c#L130-145](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/lockcmds.c#L130)

[src/backend/commands/vacuum.c#L835-880](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/vacuum.c#L835)

结构化[证据记录](../../data/evidence/55p03.json)保存固定消息角色、维护 severity 以及已接受的运行产物。
