# 08P01 — 协议违规（protocol_violation）

> PostgreSQL SQLSTATE 08P01（协议违规，protocol_violation）的源码证据、诊断与处理参考。
---

# 08P01 — 协议违规

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

SQLSTATE `08P01` 是 Class `08` 中的 **protocol_violation**。`08P01` 表示前端/后端消息违反线路协议。选定原始 TCP 案例先完成启动并观察 ReadyForQuery（`Z`），再在合法帧中发送未知前端字节 `Y`，收到 C=`08P01`、S=`FATAL`、主报文 `invalid frontend message type 89`，并在客户端关闭 socket 前读到服务器 EOF。

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

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `08P01` |
| 条件名 | `protocol_violation` |
| 状态 | `有效` |
| 已知存在于 | `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_PROTOCOL_VIOLATION` |
| 别名 | `—` |

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

## 含义 {#meaning}

服务器协议解析器先接受启动交换，随后在派发 SQL 前拒绝未知的前端消息类型。选定路径为 FATAL `invalid frontend message type 89`，之后服务器发送 EOF；因此它是受影响 socket 的同步边界，不是 SQL 解析错误。

关键边界在于线路状态，而不是 SQL 文本。客户端可以已经到达 `ReadyForQuery`，却在下一条消息中发送错误类型，选定案例正是如此；启动帧损坏或代理改变帧边界也可能更早失败，并且可能没有可用会话。选定运行覆盖未知消息分派分支；下面的固定源码证据还覆盖不同的 Bind 和 message-buffer 解析分支，其他传输故障必须依据自身的 ErrorResponse 或日志判断。

`08P01` 并不意味着固定的严重级别或 socket 结果。在固定的 extended-query 路径中，Bind 消息的 parameter-format 数量或参数数量不匹配时报告 `ERROR`；顶层循环中止当前命令，等待协议规定的 `Sync`，然后才继续下一个 `ReadyForQuery`。固定的 message-buffer 读取器在缺少字节、字符串无效或报文尾部有数据时也使用 `ERROR`。这些可恢复的协议错误不同于未知消息类型：后者是 `FATAL` 边界，因为服务器不能再信任消息同步状态。

## 诊断 {#diagnosis}

检查驱动或代理的协议版本、消息类型、帧长度、启动模式和连接所有权。选定证据中的 `collector` 是收集原始协议 ErrorResponse（C/S/M 及服务器 EOF）的对象，不是 csvlog/jsonlog。服务器关闭产生 FATAL 的 socket，案例不会复用它；独立的运行器连接能执行 `SELECT 1` 并保持 `IDLE`。这是线路协议边界，不是 SQL 语法，也不是客户端自行创建的 SQLSTATE。

按阶段解释恢复：

- 启动完成前没有会话级事务，驱动可能只有连接异常；如果服务器发过响应，应保留原始线路记录。
- 启动到达 `Z` 后，未知前端消息会在已建立后端中被拒绝，但选定分支是 `FATAL`，随后服务器关闭该 socket。即使客户端在协议失步前已经开始工作，也不能在这个 socket 上发送 `ROLLBACK`。
- 经过连接池或代理时，要比较所有者实际发送的字节与服务器期望的协议版本和帧格式。另一条连接上的 `SELECT 1` 只证明可达，不证明在途请求已经提交。

先根据主报文识别分支，再选择恢复动作。`bind message has ... parameter formats` 和 `bind message supplies ... parameters` 指向 extended-query 数量契约；`no data left in message`、`insufficient data left in message`、`invalid string in message` 和 `invalid message format` 指向报文主体或边界损坏。这些源码定义的 `ERROR` 变体在驱动仍能保持帧边界时可能通过 `Sync` 恢复；带 `FATAL` 的 `invalid frontend message type ...` 则表示选定 socket 已经失效。

## 处理 {#response}

收到 `FATAL` 或服务器 EOF 时，关闭并丢弃受影响的 socket，修正协议或代理帧后重新连接。extended-query 的 `ERROR` 应让驱动发送协议有效的 `Sync` 并等待 `ReadyForQuery`，然后检查事务状态；`Sync` 不会回滚失败的显式事务，状态为 `E` 时仍须执行 `ROLLBACK` 或回到有意建立的保存点。若非幂等请求在途中，重放前先核对结果。独立探针只证明新连接可用。

将选定案例视为会话终止分支：读到服务器 EOF 后建立新连接，并且只重做已核对且具幂等性的操作。如果违规发生在会话建立前，应先修正启动或代理配置。只有客户端解析器异常或 socket 关闭而没有服务器 `C` 字段，不能把事件标成 `08P01`。

不要自行拼接字节，也不要把事务当作已经提交。若驱动无法保持报文边界或不实现 Sync 契约，应丢弃连接并核对请求结果。这一源码流程不改变选定未知字节运行：该案例的 `FATAL` socket 必须丢弃。

## 报文 {#messages}

固定的协议解析路径以 `FATAL` 发出主报文 `invalid frontend message type %d`，其中字节数值是动态的。选定的 `Y` 字节记录为 `89`。SQLSTATE 的依据是原始 ErrorResponse，而不是客户端异常。

其他固定源码分支以 `ERROR` 发出以下主报文模板：

- `bind message has %d parameter formats but %d parameters`
- `bind message supplies %d parameters, but prepared statement "%s" requires %d`
- `no data left in message`
- `insufficient data left in message`
- `invalid string in message`
- `invalid message format`

这些 Bind 和 message-buffer 变体在本页中是源码证据，不是新增运行观察，也不会全部终止 socket。

选定的线路顺序是：启动交换 → `Z` ReadyForQuery → 合法帧中的类型字节 `Y`（十进制 `89`）→ ErrorResponse `C=08P01`、`S=FATAL`、`M=invalid frontend message type 89` → 服务器 EOF。collector 直接收集这些协议字段和 EOF，既不是 csvlog/jsonlog 记录，也不是后续独立探针。

## 代表案例 {#case}

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

SQL 探针用于检查独立连接。真实触发是运行器拥有的 TCP 握手后发送未知前端消息字节，不能只用 SQL 表达。

```sql
SELECT 1;
```

<!-- END SQLSTATE SNIPPET -->

18.6 服务器记录为 C=`08P01`、S=`FATAL`、主报文 `invalid frontend message type 89`；启动到达 `Z`，服务器 EOF 断言为 `True`，独立探针返回 `1`，状态 `IDLE`。因此没有复用触发错误的连接。

可下载的案例与证据投影分别是 [`08P01 案例 JSON`](../../data/cases/08p01.json) 和 [`作者证据`](../../data/evidence/08p01.json)。运行器清单为 `verify/cases/08P01/cases.json`；发布前会将页面 SQL 与共享注册表比对。

## 版本 {#versions}

上面的生成事实表记录锁定的目录快照和最早观察到的定义。本页自然运行范围是 PostgreSQL 18.6 与 10.21，不能据此推断所有中间版本的行为。

## 相关 {#related}

- [`08001` — sqlclient_unable_to_establish_sqlconnection](../08001/)
- [`28000` — invalid_authorization_specification](../28000/)

## 来源 {#sources}

- `src.protocol-invalid-frontend.18.6` — `src/backend/tcop/postgres.c` at `REL_18_6` commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; fixed blob SHA-256 `9fb62275b1badf94d01ab351337b60410cd9b3ab1fe63fa9f23d6d2185a21061` ([source](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/tcop/postgres.c#L454-L456)).
- `src.protocol-invalid-frontend.10.23` — `src/backend/tcop/postgres.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `badfe30749794afa80a799dcdbd142fd4e4732ac1c913002d365549ae5313497` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/tcop/postgres.c#L430-L432)).
- `src.postgres-bind.10.23` — `src/backend/tcop/postgres.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `badfe30749794afa80a799dcdbd142fd4e4732ac1c913002d365549ae5313497` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/tcop/postgres.c#L1483-L1580)).
- `src.pqformat.10.23` — `src/backend/libpq/pqformat.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `637923dbf2b9d9a0610350784f3b1d7d36a545cd8a6bd2cbe6272d9549873a91` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/libpq/pqformat.c#L432-L682)).
- `src.postgres-sync.10.23` — `src/backend/tcop/postgres.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `badfe30749794afa80a799dcdbd142fd4e4732ac1c913002d365549ae5313497` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/tcop/postgres.c#L3863-L3980)).
- `src.calls.REL_18_6` / `src.calls.REL_10_23` — fixed local call scans, SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf` / `00d16d3eb01b71ccf1b245c8f3102f9d0ec9f36fb02777b8dd1b99fcb263040c`; these scans preserve the resolved call context used by the claims.
