DEBUG.WIKI
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →
雨云;给项目一台云服务器
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →

Knex 事务报 Query read timeout,后续事务却提交了先前写入

Knex3.3.0与pg非pipeline客户端超时可能让结束语句未发送,连接仍回池。区分客户端和服务器超时,隔离状态不明连接,不把Promise失败当回滚或重放依据。

事务已经拒绝,错误是 ROLLBACK - Query read timeout 或 COMMIT; - Query read timeout,后续一个无关事务却提交了前一批写入。这时不能把“Promise 已失败”当成数据库已回滚,要立即检查同一连接是否带着未结束事务回到了池中。

这里限定 Knex 3.3.0、pg 8.23.1、PostgreSQL,且 pg 未启用 pipeline 的客户端 query_timeout 场景。查询超时只在客户端结束等待,服务器语句仍可能继续执行;排在它后面的 ROLLBACK、COMMIT 也可能先在客户端队列超时,根本没有发往服务器。

后一个 BEGIN 未必开启新事务

Knex 3.3.0 的事务查询记录失败后,会结束事务对象的 Promise;连接获取逻辑在 finally 中仍释放连接。默认 PostgreSQL 连接校验没有检查会话当前是否处于事务内。

如果服务器的前一个事务仍然打开,后面的 BEGIN 可能只得到“there is already a transaction in progress”的警告,随后无关工作的 COMMIT 就连带提交之前留下的写入。另一种表现是连接进入失败事务状态,后续请求连续报 25P02、current transaction is aborted。

先保留事务的第一条错误、结束语句错误、连接或后端标识及服务器日志。错误文本相同不代表一定发生了跨事务污染;服务器已经明确返回的提交失败,与 COMMIT 从未发送,是不同状态。不要在应用异常分支直接重放扣款、订单等非幂等写入。

调整这一类事务的超时来源

若当前需求是限制 PostgreSQL 语句耗时,可以在该业务所拥有的专用 Knex 实例中,避免用 pg 客户端 query_timeout 截断事务协议,改由服务端 statement_timeout 约束语句。下面仅示意连接选项,保留实际认证和连接管理配置;5000 毫秒与池上限都需要按业务预算选择:

import knex from 'knex';

const db = knex({
  client: 'pg',
  connection: {
    connectionString: process.env.DATABASE_URL,
    query_timeout: 0,
    statement_timeout: 5000,
  },
  pool: { min: 0, max: 5 },
});

该片段以 pg 默认值未被全局改写为前提,连接串和单条查询也不能另带 query_timeout。应核对最终解析配置,不能只在一层写零就认为覆盖了其他设置。

query_timeout 为零关闭的是 pg 的客户端查询计时器;服务端超时会返回语句错误,事务处理仍需等待并完成清理。事务 handler 不应吞掉语句失败后当作成功返回。HTTP 请求已经超时,也不能据此认为其数据库工作已经取消、回滚或可以重复提交。

这项配置减少的是“客户端排队中的结束语句先超时而未发送”的具体触发条件,不是对所有网络中断、提交结果未知或事务泄漏的完整修复。移除客户端查询期限后,应用仍需保留连接建立、网络故障、任务生命周期与外层资源预算;不能把长期无响应的工作变成无人管理的无限等待。

修改已有共享实例前,应先划清所有者与受影响业务,并按受控部署流程处理;不要对正在服务其他请求的共享池随手 destroy。已经受污染的连接也不会因配置文件改了而自动恢复,应停止将状态不明的连接交给新工作,由连接所有者隔离和清理,再核对已发生的业务结果。

I 状态也不能证明前一个业务成功

pg 8.21 起提供 getTransactionStatus,返回最近 ReadyForQuery 中的 I、T、E。I 表示当时处于空闲状态,T 表示事务中,E 表示失败事务中;它不是本次 COMMIT 成功的业务凭据,而且最近状态可能尚未反映正在处理的消息。

只在 Knex 自有池加 validate 检查 I,可以过滤部分明显非空闲连接,但不能据此保证所有未知状态都安全;外部 connectionPool 还不走同一校验入口。不要把这一局部检查描述为已经修好 Knex,也不要直接写私有 disposed 标记绕过连接所有者。

若只有失败事务报错,可先按事务出错后的处理顺序核对第一条错误;本场景还必须检查连接是否被错误复用,单次 ROLLBACK 文本出现在客户端日志中并不证明服务器收到它。

在隔离数据库中沿原有获准流程,核对结束语句确实到达、连接不带旧事务交给下一请求,并从独立会话确认提交结果。先前超时的业务仍需按业务唯一标识核对,不能让下一次无关事务决定它的最终状态。