题干与适用场景
一个使用 prepared statement 的 API 在上线后出现长尾延迟:小租户查询很快,大租户查询却突然走顺序扫描。请说明 PostgreSQL 的 custom plan 与 generic plan 如何选择,如何用 EXPLAIN (GENERICPLAN) 比较计划,如何验证统计信息、参数分布、缓存与 plancache_mode,并给出不破坏事务和连接池的修复流程。
面试官考察点
- 能否区分规划阶段、执行阶段与结果传输成本。
- 是否理解 generic plan 不依赖具体参数值,而 custom plan 可以利用参数选择性。
- 能否安全使用
EXPLAIN ANALYZE,避免把写入副作用带到生产。 - 能否结合统计信息、索引、连接池和参数化策略定位回归。
- 能否用证据选择
auto、forcegenericplan或forcecustomplan。
回答前需要澄清的问题
- 查询是通过 prepared statement、ORM 还是代理层执行?连接是否复用?
- 参数分布是否倾斜,租户规模和数据冷热是否差异显著?
- 延迟回归发生在规划、执行、锁等待、IO 还是序列化?
- 是否允许调整索引、统计目标、SQL、连接池或会话级参数?
30 秒回答框架
先用 EXPLAIN (GENERICPLAN) 查看不依赖参数的计划,再对代表性参数用 EXPLAIN ANALYZE EXECUTE 查看 custom plan 和实际行数。generic plan 能省规划时间,但参数选择性差异大时可能长期低效。先确认统计信息和计划缓存行为,再用基准数据比较规划成本、执行成本和尾延迟,最后在受控会话中调整 plancache_mode 或改写查询,并验证连接池中的所有连接。
分步骤深入解答
1. 拆分规划与执行
规划器根据 SQL、统计信息和参数决定扫描与连接算法;执行器再读取页面、过滤行并返回结果。只看应用总耗时无法判断 generic plan 是否是根因。
2. 说明 custom plan
custom plan 针对本次参数生成,能利用选择性估计。例如少量租户可能适合索引扫描,大租户可能适合顺序扫描或不同的 join 顺序;代价是每次或多次重新规划。
3. 说明 generic plan
generic plan 使用参数占位符,不依赖本次值。它可以摊薄规划开销,但如果参数分布高度倾斜,单一计划可能对多数值都不理想。EXPLAIN (GENERIC_PLAN) 不能与 ANALYZE 同时使用。
4. 先看 generic plan
EXPLAIN (GENERIC_PLAN)
SELECT sum(amount)
FROM invoices
WHERE tenant_id = $1 AND status = $2;检查扫描类型、估算行数、索引条件、连接顺序和总成本。参数类型无法推断时显式写 cast,避免把类型问题误判成计划问题。
5. 再看代表性 custom plan
在隔离环境中使用不同规模租户的参数执行 EXPLAIN (ANALYZE, BUFFERS) EXECUTE。关注 estimated rows 与 actual rows、shared hits/reads、规划时间、执行时间和是否出现磁盘排序;不要只比较 cost 数字。
6. 检查统计信息与分布
确认 autovacuum 或手动 ANALYZE 已覆盖近期变更,检查列的基数、相关性和最常见值。对倾斜列可评估提高统计目标,但要以规划准确度和规划开销的实测结果为依据。
7. 选择修复边界
会话级 plancachemode=forcecustomplan 可验证 custom plan 是否解决回归;forcegenericplan 适合计划稳定且规划成本高的查询。长期方案可能是索引、查询拆分、显式类型或让 ORM 避免不必要的 prepared statement,不能只改全局参数。
8. 验证连接池与发布
连接池会让会话级设置、prepared statement 生命周期和 PostgreSQL 版本差异变得重要。灰度时按参数分位、租户规模、连接池实例和数据库节点比较 p95/p99、规划时间、缓冲命中和错误率,并准备回滚。
设计取舍与边界
generic plan 的收益是减少规划工作,代价是丢失参数值带来的选择性信息;custom plan 则可能在高频短查询中浪费规划 CPU。EXPLAIN ANALYZE 会实际执行语句,写入类语句应在事务中回滚或使用只读副本。计划成本是估计单位,不能直接当作毫秒;统计信息是抽样结果,计划会随数据、版本和 ANALYZE 改变。
落地计划与证据
- 记录查询文本、参数类型、连接池模式、PostgreSQL 版本和计划缓存行为。
- 用
GENERIC_PLAN与多个代表性参数的ANALYZE计划建立基线。 - 检查统计信息更新时间、估算误差、索引命中、IO 和规划时间。
- 在单连接或灰度会话中试验
plancachemode,避免直接修改全局配置。 - 以 p95/p99、规划 CPU、shared reads、锁等待和错误率验收,并保留回滚开关。
常见误区与追问
误区一:看到顺序扫描就删掉 generic plan
顺序扫描可能是大范围结果的正确选择。先比较代表性参数的实际行数、IO 和尾延迟。
误区二:把 cost 当作真实时间
cost 是规划器的相对估计单位。应结合 ANALYZE 的 actual time、buffers 和线上指标判断。
误区三:在生产直接运行写入型 EXPLAIN ANALYZE
ANALYZE 会执行语句。写入操作要在可回滚事务或隔离副本中验证。
误区四:只调高统计目标
统计目标会增加分析和规划成本,也不一定解决连接池或参数类型问题。必须用基准数据证明收益。
误区五:忽略连接池会话边界
会话级 plancachemode 和 prepared statement 可能只影响部分连接。发布前要覆盖池中所有连接和回收策略。