7.5 参数、缓存计划与计划漂移
同一 query shape 可能面对完全不同参数选择率。每次用具体值规划可得到针对性路径,却支付 planning 成本;复用 generic plan 可省 planning,却看不到具体参数。缓存计划是成本取舍,不是“prepared statement 必然更快”。
7.5.1 自定义计划与通用计划
custom plan 把本次参数代入后规划,可使用 MCV、partition bounds 等具体信息;generic plan 保留 $1,适合参数分布相近或 planning 昂贵的重复语句。
在 plan_cache_mode=auto 下,带参数的 prepared statement 前五次使用 custom plan;之后服务器生成 generic plan,并把它的估算成本与前五次 custom plan 的平均估算成本比较。只有 generic plan 并未贵到值得反复重新规划时,后续执行才会复用它。这个启发式使用规划器成本,不等于实际执行时延;因此还要用代表性参数分别测量 planning time、execution time 与结果行数。force_custom_plan / force_generic_plan 只适合诊断对照,不应在缺少 workload 证据时全局强制。
prepared statement 是 session 对象。DDL、统计变化和影响解析/规划的设置可触发 re-plan;search_path 变化也会重新解析。transaction pooling 下 client 与 server session 生命周期不同,driver/PgBouncer 的 prepared statement 支持与配置必须单独验证,不能从裸 PostgreSQL session 外推。
7.5.2 预备语句、数据倾斜与参数敏感
fixture:
强制 custom:
强制 generic:
具体 node type 受表宽、cache、cost constants 与版本影响,不作为硬断言;真正的参数敏感证据是 custom estimate 能区分 90000/10,而 generic 必须使用同一估计。hot generic 路径即使本次仍足够快,也已经暴露风险:平均延迟可能掩盖少数大 tenant 的尾延迟。
处理方案按证据选择:
- 保持 auto,并观察 generic/custom 计数与参数分布;
- 对真正参数敏感的 workload 分 query shape 或显式选择策略;
- 改 schema/index/partition,使不同选择率下都可接受;
- 对 hot tenant 单独路由/批处理;
- 局部 force custom,并量化 planning 开销。
不要把 tenant literal 拼进 SQL 来“骗 custom plan”;这会扩大 query shape、失去安全参数绑定并污染统计。
7.5.3 计划变化是症状,不自动等于回归
计划会因以下因素合理变化:
回归判断顺序:
- result contract 是否仍正确;
- 同 workload 的 latency/throughput/resource/SLO 是否变差;
- wait 还是 executor 在耗时;
- estimate/actual 从哪里开始偏;
- 数据、statistics、settings 与参数是否可比;
- 新 plan 是否只是节点不同但成本更好;
- 改动是否增加写放大、WAL、memory 或尾延迟。
对计划做 canonical fingerprint 可以帮助发现变化,但不能把完整 JSON bytes 当稳定 API;版本会新增字段,cost/time 天生动态。保留原始计划,同时抽取 query identity、node responsibilities、cardinality error、buffers/spill/WAL 与环境 facts。
应急时可以用 planner GUC 证明替代路径,长期方案仍要回到统计、SQL、index、参数策略或版本 bug。第 8 章会把这里的计划证据放进慢查询闭环,第 9 章再审查 index 收益和写成本。
上一节:分区裁剪的两种时机 · 返回本章目录 · 下一节:建立计划证据基线 · 查看全书目录 · 查看索引中心