DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
晚安云;香港 CN2 ¥15/月
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →

pgx 查询已取消,却返回 write failed: i/o timeout

取消恰好落在套接字写入阶段时,errors.Is 可能识别不到 context.Canceled。保留驱动错误和 context 状态,避免把不确定写入直接重试。

调用方已经 cancel,pgx 却偶尔返回 write failed: ...: i/o timeout;同一任务多数时候又返回 context canceled。如果依赖 errors.Is(err, context.Canceled) 分类,这种时序差异可能被误记为网络故障,甚至触发重试。

pgx 5.10.0、5.11.0 有这条写入侧边界。正式 5.11 在部分查询的 flush 失败后直接返回写入错误,而读取错误经过 normalizeTimeoutError。取消发生在读还是写,会影响最终的错误形状。

把调用方状态与驱动错误分别留下

在错误返回的边界同时查看原 err 与当次 ctx.Err,不要先覆盖其中一个。例如下面是诊断信息的构造片段:

type Failure struct {
    DriverError error
    ContextError error
}

failure := Failure{
    DriverError: err,
    ContextError: ctx.Err(),
}

Failure 类型可放在包内,变量片段放在收到查询错误的函数中。它不是新的驱动错误类型,也不要求把整份错误对象直接输出给外部用户;分类与日志应沿项目已有入口处理。

ContextError 非 nil 说明观察时调用方已结束等待,并不能单独证明那次写入错误由取消引起。网络错误可能先发生,随后 context 才到期。若要提供精确的 errors.Is 语义,需要在应用自己的包装类型或明确的连接监测层设计,而不能遇到任何错误就统一返回 ctx.Err。

取消不是“数据库没有执行”的回执

写入套接字时失败,服务端可能没有收到完整请求,也可能已经接收到足以执行的内容。更不能由提交阶段的超时,推断事务一定没提交。对写操作应先按幂等键、业务状态或事务结果核实,再决定重试。

只读查询的重试也要保留原有次数与期限,不能无限重放来等一个理想错误文本。修改错误分类后,连接本身仍由 pgx 和池按正常失败路径处理;不要继续使用底层已被关闭的连接。

用阶段信息核对,不只匹配字符串

在隔离环境分别覆盖写入被阻塞时取消、等待读取时取消、真正网络超时和正常完成,确认应用能够区分调用方取消与未知连接失败,同时保留原始原因。不要用把生产网络堵住的方式制造条件。

截至 5.11.0,上述正式写入路径仍不能保证统一成 context.Canceled。保留原分类逻辑和脱敏诊断样本,局部切换后观察告警与重试是否按业务要求变化;撤回分类改动不会撤销已经执行或重试过的数据库操作。