pgx Prepare 失败后,同一 SQL 一直报 42P05
database/sql 预备语句在 Describe 阶段失败,后续查询可能清理错名字并报旧 SQL。正式 5.11 修正服务端语句名的保存,需同时核对版本与连接状态。
某条预备语句失败后,同一 SQL 在同一连接上反复报 42P05 duplicate_prepared_statement。另一条毫无关系的查询又出现 failed to deallocate previously failed statement,消息里引用的是之前的 SQL。若应用经过 pgx 的 database/sql 适配器,或使用 GORM 的 PrepareStmt,应检查旧版本的失败清理路径。
这条故障在 pgx 5.10.0 有明确场景:Parse 已成功建立服务端语句,随后 Describe 阶段被取消或超时。它不同于应用主动重复使用自定义预备语句名,也不应只凭 42P05 就断言属于同一原因。
缓存键不是服务端语句名
database/sql 路径把 SQL 文本作为 Prepare 的键,pgx 再根据 SQL 生成 stmt_ 开头的 digest 名发给服务端。旧路径在失败后保存了 SQL 键,后续 Deallocate 找不到成功缓存项时,便可能把原 SQL 文本当作服务端语句名。
清理一个不存在的名字未必报错,真正的 digest 语句却还在。因此重试相同 SQL 又碰到已存在的服务端名字;清理往返本身若失败,还会把旧查询错误带给下一次借用连接的调用。
正式 5.11.0 修正了名字与 Parse 状态
5.11.0 在失败记录中保存实际发出的 psName,并且只有 ParseComplete 为真、确实建立了语句时才安排清理。项目可按支持范围采用包含该修复的正式版本,核对最终构建的 pgx 模块版本,而不是只看 go.mod 中的手工编辑。
升级需要保留 go.mod、go.sum 与原部署产物。5.11 还改变了部分连接 URI 解析规则,尤其密码含@的连接 URL应在同轮兼容核对,不能修好清理又连错目标数据库。
已处于异常状态的连接,需要通过应用既有池排空或连接替换流程恢复;新库不会追溯改写旧进程里的失败记录。不要给每次请求增加全局 DEALLOCATE ALL,它可能使应用缓存与服务端状态再次不一致,也不能无条件重放原来的写操作。
复查失败之后的下一条查询
在隔离环境覆盖 Parse 前失败、Parse 后 Describe 失败、相同 SQL 重试,以及不同 SQL 随后借用连接的情况。确认正常失败仍返回原始原因,清理不会继续引用错误名字,也没有残留的重复预备语句。
仅执行健康连接上的一次 SELECT,绕开了这条路径。对原失败操作仍需核对业务结果、事务状态与幂等记录;驱动清理恢复,不等于那条业务操作已经成功。若撤回依赖升级,恢复完整产物与连接配置,并保留触发条件以便继续评估。


