← 中文资源

简体中文版本

PostgreSQL — ERROR:语句超时导致取消

排查 PostgreSQL 的 canceling statement due to statement timeout,找到设定截止时间的层,测量查询真实成本,并修计划或修预算。

阅读英文原文

症状

  错误自带触发规则:canceling statement due to statement timeout,一致的失败时长就是指纹——恰好 30 秒失败意味着某处有个 30 秒预算。要弄清是谁的预算,因为这个设置可以从四个地方来:服务器配置、角色、数据库、连接选项串。同一个查询在 psql 手工跑成功、经应用驱动跑失败,几乎总是驱动连接串里的 options 带了 statement_timeout。然后用 SET statement_timeout = 0 加 EXPLAIN ANALYZE 关掉护栏测真相,结果分两条路:真需要 4 分钟(预算问题,按作用域调高)或因计划翻转为顺序扫描才需要 4 分钟(成本问题,修计划)。第三种变体藏在 pg_stat_activity:wait_event 为 Lock 说明预算在执行开始前就被锁等待烧光了。

诊断思路

  先确认失败会话的有效超时与来源;再无界测量真实成本与计划(锁等待显示为等待事件、计划问题显示为扫描形状);最后区分锁受害者——超时只是替罪羊。报表/分析端点聚集失败而 OLTP 健康、查询随数据增长逐步逼近预算,都是常见时间线。

根因

  statement_timeout 限定该会话单条语句的墙钟时间。当查询需要超过会话预算——真实成本更高、计划回归、或锁等待先耗尽了配额——PostgreSQL 在截止时刻以这个确切的错误取消它。

修复与验证

  按工作负载类别分域预算:OLTP 角色设 5 秒、报表角色设 300 秒,或混合池里按连接 SET;成本确实高就修计划(建索引)或做物化汇总(MATERIALIZED VIEW 加 CONCURRENTLY 刷新);锁受害者单独立案——找到持有者修持锁模式,给排队查询调高超时只是把队伍拉长。验证时报表在专属预算内完整跑完、OLTP 保留紧凑护栏、该错误只出现在真正超出类别预算的查询上。全局设 0 会拆除所有会话的防护——按角色或按连接分域,绝不集群级一刀切。

代码示例

ERROR: canceling statement due to statement timeout
SHOW statement_timeout;                -- effective now
ALTER ROLE reporting SET statement_timeout = '300s';
SET statement_timeout = 0;
EXPLAIN (ANALYZE, BUFFERS) SELECT ... ;   -- real duration and plan
ALTER ROLE oltp_app     SET statement_timeout = '5s';
ALTER ROLE reporting    SET statement_timeout = '300s';
# or per connection, for mixed pools:
conn.execute("SET statement_timeout = '300s'")   # this session only
CREATE INDEX orders_report_idx ON orders (tenant_id, created_at);
-- or pre-aggregate:
CREATE MATERIALIZED VIEW daily_totals AS SELECT ... ;
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_totals;