# 25006 — 只读 SQL 事务（read_only_sql_transaction）

> PostgreSQL SQLSTATE 25006：只读 SQL 事务失败、诊断与修复所需的事务边界。
---

# 25006 — 只读 SQL 事务（read_only_sql_transaction）

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

`25006` 表示命令试图在只读事务中写入。选定案例在 `SET TRANSACTION READ ONLY` 后执行 `CREATE TABLE`，观察到 `INERROR`，回滚后离开只读事务再建表。

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

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `25006` |
| 条件名 | `read_only_sql_transaction` |
| 状态 | `有效` |
| 已知存在于 | `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_READ_ONLY_SQL_TRANSACTION` |
| 别名 | `—` |

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

## 含义 {#meaning}

该码覆盖多个保护点。`PreventCommandIfReadOnly` 把命令名填入 `cannot execute %s in a read-only transaction`；固定源码还包含恢复期间临时表和复制原点的专用报文。选定案例覆盖普通显式只读事务路径，而备用机或恢复路径可能使用不同的命令相关报文。

## 诊断 {#diagnosis}

记录精确命令、SQLSTATE、严重级别和事务状态。重试前检查有效的 `SHOW transaction_read_only`，并在需要判断服务器边界时检查 `pg_is_in_recovery()`；同时确认连接池是否把连接路由到了备用机。选定运行中 `CREATE TABLE` 返回 25006，使显式事务块进入 `INERROR`；复用会话前必须 `ROLLBACK`。离开只读事务后再次 `CREATE TABLE` 成功。

## 处理 {#response}

先判断操作是否应放在只读事务中。若必须写入，应在主库的可写事务中执行或移出只读块；失败块先回滚，再把连接归还连接池。备用机或恢复期间的 25006 需要把操作路由到主库；在同一只读目标上重试写入不会改变访问模式。

## 源码报文 {#messages}

核心 utility 路径的源码模板是 `cannot execute %s in a read-only transaction`；在恢复目标上，同一 utility 保护会使用 `cannot execute %s during recovery`，恢复期间临时表和复制原点也有各自的固定文本。`%s` 是实际命令名，不能脱离路径当作一条静态报文。

## 实测诊断 {#observed}

`18.6 (Homebrew) / latest`：SQLSTATE `25006`；主报文 `cannot execute CREATE TABLE in a read-only transaction`；状态 `INERROR → IDLE`；修复后关系行数 `0`；最终状态 `IDLE`。
`10.21 (Debian 10.21-1.pgdg90+1) / pg10`：SQLSTATE `25006`；主报文 `cannot execute CREATE TABLE in a read-only transaction`；状态 `INERROR → IDLE`；修复后关系行数 `0`；最终状态 `IDLE`。

## 代表案例 {#case}

运行器从共享语句清单（registry）读取下列 setup、只读事务、回滚和事务块外修复语句；完整断言、环境和清理见 [案例导出](../../data/cases/25006.json)。

<!-- BEGIN SQLSTATE SNIPPET: read_only_transaction -->
```sql
-- create
CREATE TABLE items(id integer PRIMARY KEY, note text NOT NULL);
-- begin
BEGIN;
-- read_only
SET TRANSACTION READ ONLY;
-- trigger
CREATE TABLE blocked(id integer PRIMARY KEY);
-- rollback
ROLLBACK;
-- repair
CREATE TABLE blocked(id integer PRIMARY KEY);
-- verify
SELECT count(*) FROM blocked;
```
<!-- END SQLSTATE SNIPPET -->

上述片段的 SQLSTATE、诊断、状态和修复断言来自共享语句清单（registry）（SHA-256 `62db30e401b1f72fa50958d0ac612b2b1eb636299532dd1ad246c167e4f9fadf`）；[结构化证据](../../data/evidence/25006.json)。

作者证据 ID：`identity`, `utility-path`, `other-paths`, `runtime`。选定运行记录：`runtime.25006-batch2-latest-20260909.latest`, `runtime.25006-batch2-pg10-20260909.pg10`。
## 版本与边界 {#versions}

选定的 `CREATE TABLE` 案例在 PostgreSQL 18.6 与 10.21 通过，主报文相同，并完成 `INERROR → IDLE` 恢复和修复后建表。其他 25006 源码路径不在本运行案例范围内。

## 相关 {#related}

[25P02 失败 SQL 事务](../25p02/)、[25001 活动 SQL 事务](../25001/)、[25P03 事务空闲超时](../25p03/)。

## 来源 {#sources}

- [`src.errcodes.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt) (SHA-256 `6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba`)
- `src.calls.REL_18_6` — `raw/calls/REL_18_6.jsonl` (SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf`)
- [`src.utility.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/tcop/utility.c#L397-L448) (SHA-256 `7aae5d07628b6debf8456d1d4ea96f28912232192e56ea25773b4c4b61235a00`)
- [`src.namespace.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/catalog/namespace.c#L4418-L4437) (SHA-256 `8c9e6a99e84fa2cec8a9b1de2ecbe3966a13f4ad68c6ccfbfb8134a754d73e56`)
- [`src.origin.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/replication/logical/origin.c#L189-L200) (SHA-256 `81e5d5b4539b67bb372f0f0a05395af900c322cdbcec8a4b1f358333a16e6518`)
- [`doc.set-transaction.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/doc/src/sgml/ref/set_transaction.sgml#L131-L145) (SHA-256 `33554463a2c9da1cf2c72cc27d4647d557204bb13a03cfeccb1b83f237a46589`) · [official documentation](https://www.postgresql.org/docs/18/sql-set-transaction.html)
