指南
先读协议诊断
先确认协议消息类型。PostgreSQL 用 ErrorResponse 表示错误,用 NoticeResponse 表示通知;两者都携带以零字节结束的带标识字段。C 是不可本地化的 SQLSTATE,M 是主消息,D/H 分别是可选 detail 与 hint。S 可能本地化,V 是服务器提供时的未本地化 severity。schema(s)、table(t)、column(c)、constraint(n)、position(P)、context(W)、源码文件(F)、行号(L)和 routine(R)等字段都是有条件的;有就保留,没有就不要推断。参见 PostgreSQL 18 错误与通知字段 和消息格式。
目录类别只是分类。决定语句是否失败前,要先读取实际协议严重性和消息类型。WARNING 或 NOTICE 可以作为通知送达,同时命令仍然完成;ERROR 作为错误送达,通常会改变显式事务状态。成功的 CommandComplete 只有命令标签,不携带 ErrorResponse SQLSTATE。把服务器版本和操作一起保留,因为相同 SQLSTATE 可能对应不同源码路径。
恢复正确的事务边界
自动提交为每条语句建立独立事务边界。语句错误后,连接通常仍可执行下一条语句。显式事务中的未处理 ERROR 会使事务进入失败状态;之后普通命令会被 PostgreSQL 以 25P02 拒绝,直到回滚。25P02 是后续观察,不是第一条错误的替代品。实际边界应参照 ROLLBACK 和 SAVEPOINT 文档。
如果一个已经存在的保存点包围了可选内部单元,则执行 ROLLBACK TO SAVEPOINT name,然后继续外层事务或释放保存点;保存点以前的工作会保留。没有这样的保存点时,ROLLBACK 会结束失败事务,重试必须从新事务开始。已接受的 25P02 案例 展示根错误 23505、后续 25P02 和恢复;23505 证据 记录更窄的保存点边界。
PL/pgSQL 的 EXCEPTION 块提供另一种窄边界。进入处理器前,块内变更会回滚;块以前的外层变更保留。处理器中,SQLSTATE 和 SQLERRM 表示当前异常;GET STACKED DIAGNOSTICS 可读取 RETURNED_SQLSTATE、主消息、detail、hint、context 和对象字段。参见错误捕获、堆叠诊断 和RAISE。处理器应只包围能够安全分类的操作;WHEN OTHERS 不应抹掉原始字段。
只有在结果边界明确时才重试完整单元
重试是业务决策,不是 SQLSTATE 类别的属性。若序列化失败或死锁等原因具有瞬时性且操作可安全重复,应从新快照重试整个事务。不要只重放最后一条语句,因为前面的读取、写入、锁、通知或外部调用可能共同构成一个业务单元。客户端超时或在收到结果前失联时,服务器可能已经提交但客户端没有响应,完成状态不确定;对外部可见操作应使用幂等键、持久业务键或状态查询后再重复。
遇到权限错误、对象不存在或无效数据时,先检查并修复权限、对象或输入,再判断修正后的操作是否安全。已经完成命令的警告不应触发自动重放。保留第一条诊断和事务状态;随后出现的 25P02 应作为后果记录。退避和尝试次数上限可以控制负载,却不能使非幂等操作变得安全。服务器明确报告超时或取消表示操作失败;客户端在获知结果前失联则属于完成状态不确定的另一类情况。
关联应用与服务器日志
在格式化消息前,记录 SQLSTATE、未本地化 severity、主消息、detail、hint、对象名称、position/context、服务器版本、可用时的 backend PID,以及客户端事务状态。添加应用请求或幂等键和时间戳,便于在服务器日志中找到同一操作。PID、session 标识和 query ID 是关联线索,不能替代协议字段。
CSV 和 JSON 服务器日志是结构化输出,但形状不等同于 ErrorResponse。PostgreSQL 18 的 csvlog 列包含 severity、SQLSTATE、message、detail、hint、context、query、源码位置、application name、backend type 和 query ID。jsonlog 输出 JSON,并可能省略 null 字段;处理器应忽略未来新增字段。按日志文档配置 log_destination 和 logging_collector,再按 PID/session/request 关联,不要假定 CSV 列名或 JSON 键名与协议字段相同。
PL/pgSQL 与自定义条件
RAISE 可以报告 DEBUG、LOG、INFO、NOTICE、WARNING 或 EXCEPTION;EXCEPTION 通常中止当前事务,其他级别只发送消息。它可以指定条件名或五字符 SQLSTATE,并通过 USING 设置 MESSAGE、DETAIL、HINT、SCHEMA、TABLE、COLUMN、DATATYPE 或 CONSTRAINT。PostgreSQL 允许使用除 00000 外、由数字和大写 ASCII 字母组成的任意五字符代码,包括自定义代码。末三位全为零的代码是类别码,只能用整个类别捕获。参见 RAISE 语法。
命名处理器匹配指定条件及其别名;类处理器范围更宽。WHEN OTHERS 捕获除 QUERY_CANCELED 和 ASSERT_FAILURE 以外的所有错误条件;通知级消息不是异常,因此不会被它捕获。PL/pgSQL 主动抛出的自定义代码只能证明该函数有意报告,不能证明 core、contrib、FDW、ECPG 或 driver 自然产生同一代码。
客户端如何暴露诊断
下表固定到已检查的发行版或主文档;不声称本项目对六个客户端都做过运行测试。
| 客户端 | 检查版本/来源 | 暴露的诊断 | 应保留的边界 |
|---|---|---|---|
| libpq | PostgreSQL 18.6,commit 724edf9b |
PQresultErrorField 从错误或警告 PGresult 读取字段;PQstatus 描述连接状态。 |
启动失败可能没有 PGresult;结果字段 API 不是通用启动错误 API,应分开记录连接文本和服务器日志。 |
| psycopg | 3.3.5,tag 解析到 commit ea542c95 |
Error 暴露 sqlstate、diag、pgconn 和 pgresult;同一源码构造具名 SQLSTATE 异常类。 |
连接错误可能是 sqlstate=None,失败查询则可能有结果诊断。 |
| PostgreSQL JDBC | pgjdbc REL42.7.8,commit 9a5492d9 |
PSQLException.java 将 ServerErrorMessage.getSQLState() 映射到 JDBC getSQLState(),并保留服务器消息。 |
客户端和传输异常也会被包装;要同时看异常类型和 server-message 是否存在。 |
| pgx/pgconn | pgx v5.7.6,commit a2fca037 |
errors.go 为服务器错误定义 PgError.SQLState()。 |
连接、context 和解析错误是其他 Go error 类型或包装;使用 errors.As。 |
| node-postgres / pg-protocol | node-postgres pg@8.16.3,commit 8f8e7315 |
messages.ts 与 parser.ts 将 E/N 字段解析为数据库错误和通知消息。 |
code、severity、detail、hint、对象字段来自服务器诊断;socket 错误是独立 JavaScript error,可能没有 SQLSTATE。 |
| Npgsql | Npgsql v10.0.0,commit a1802184 |
PostgresException.cs 暴露 SqlState;诊断指南 区分通知和异常类型。 |
NpgsqlException 可以包装网络/客户端失败;通知通过 Notice 事件到达,不是命令错误。 |
跨客户端看,具名异常类、常量和包装类型只是便利映射;跨客户端证据仍是协议 SQLSTATE 和字段。客户端错误消息或 null SQLSTATE 不会覆盖服务器日志中确认的代码。已有 psycopg 3.3.5/libpq 18.6 启动记录中 driver 为 null、服务器为 28P01/53300,只证明这一栈的边界,不能外推到其他五个客户端。