# 26000 — invalid_sql_statement_name（预备语句名称无效）

> PostgreSQL SQLSTATE 26000：预备语句会话归属、诊断与恢复。
---

# 26000 — invalid_sql_statement_name（预备语句名称无效）

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

`26000` 表示 PostgreSQL 被要求使用当前后端会话中不存在的命名预备语句。这是会话资源查找失败，先检查会话是否保持不变以及语句生命周期，再修改 SQL 文本。

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

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `26000` |
| 条件名 | `invalid_sql_statement_name` |
| 状态 | `有效` |
| 已知存在于 | `7.4` |
| 锁定快照 | `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_INVALID_SQL_STATEMENT_NAME, ERRCODE_UNDEFINED_PSTATEMENT` |
| 别名 | `ERRCODE_UNDEFINED_PSTATEMENT` |

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

## 含义 {#meaning}

`PREPARE` 会在一个 PostgreSQL 会话中注册命名语句；`EXECUTE` 和 `DEALLOCATE` 也必须在同一会话按名称查找。连接池可能在两次操作之间换了后端。核心 `prepare.c` 路径使用 `prepared statement "%s" does not exist`，其中名称由服务器动态填入；固定的扩展查询路径还会在未命名语句不存在时使用 `postgres.c` 的 `unnamed prepared statement does not exist`。

## 诊断 {#diagnosis}

先读取驱动异常中的 SQLSTATE 和主报文，并在 `PREPARE`、`EXECUTE` 周围记录后端 PID 或同等连接身份。在同一连接上查询 `pg_prepared_statements`，才能判断该失败会话是否存在这个名称；在另一个池连接上查询不能证明失败会话的状态。区分显式 DEALLOCATE、连接被替换和未命名语句路径，不要把它误判成 PREPARE 语法错误。

## 处理 {#response}

如果命令位于显式事务中，先回滚失败块，再继续发命令。在真正执行它的会话上重新 PREPARE，并让应用或连接池在同一次 checkout 中完成定义和参数绑定。缺少语句本身没有执行预期操作，但重复更大的业务流程前仍应按业务请求标识和副作用检查执行正常校验。

## 报文 {#messages}

固定源码模板包括 `prepare.c` 的 `prepared statement "%s" does not exist`，以及扩展查询路径 `postgres.c` 的 `unnamed prepared statement does not exist`。选定案例记录的是 `source_file = prepare.c`、`source_function = FetchPreparedStatement`；只有命名模板带服务器端名称替换。应根据 SQLSTATE 和结构化诊断分支，不要把任一报文当成稳定完整字符串。

## 代表案例 {#case}

运行器从 `verify/cases/26000/snippets.json`（SHA-256 `b9fdbb48371e0d9902cb9e055ececc4878c38422773a8a1571651d9952809032`）读取下面的序列，并保证所有语句在同一连接执行；完整结果见[公开案例导出](../../data/cases/26000.json)和[结构化证据](../../data/evidence/26000.json)。

<!-- BEGIN SQLSTATE SNIPPET: prepared_statement_recovery -->

```sql
PREPARE statement_name(integer) AS SELECT $1 + 1;
EXECUTE statement_name(1);
DEALLOCATE statement_name;
EXECUTE statement_name(1);
```
<!-- END SQLSTATE SNIPPET -->

`statement_name` 是页面中的占位符。运行器会替换为唯一名称，释放它，断言 `26000` 和 `IDLE`，随后重复 registry 中同一组 `PREPARE`、`EXECUTE`、`DEALLOCATE` 三条语句修复会话。也就是说，恢复步骤是：在同一连接重新 `PREPARE`，执行它，并在 checkout 结束时 `DEALLOCATE`；引用的是现有 `prepare`、`execute`、`deallocate` registry 语句，而不是第二套 SQL 定义。

选定运行案例在 PostgreSQL 18.6 和 10.21 观察到 `26000`：先释放同名预备语句，再在同一会话执行它，得到带动态名称的诊断，连接保持 `IDLE`；随后重新 PREPARE 并成功执行。

## 版本 {#versions}

锁定目录从早期历史边界到当前正式快照都记录了该条件。18.6 固定源码同时包含命名和未命名预备语句路径；运行比较只覆盖 18.6 与 10.21 的命名路径。不同版本的源码行和报文措辞可以变化，选定目标中的 SQLSTATE 仍为 `26000`。

## 相关 {#related}

另见[34000 游标名称无效](../34000/)和[25P02 失败事务](../25p02/)。

## 来源 {#sources}

- [`src.errcodes.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt)（SHA-256 `6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba`）
- [`src.prepare.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/prepare.c#L451-L454)（SHA-256 `e37fbd5f7618e5554561d9293d8c3af7cf3190c62c5c6bbceb3fe8b97be17956`）
- [`src.postgres.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/tcop/postgres.c#L1670-L1672)（SHA-256 `9fb62275b1badf94d01ab351337b60410cd9b3ab1fe63fa9f23d6d2185a21061`）
- [`PREPARE 官方文档`](https://www.postgresql.org/docs/18/sql-prepare.html) · 本地调用扫描 `src.calls.REL_18_6`（SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf`）
