PostgreSQL 计划缓存实战(第 10 篇):预编译 SQL 前五次都快,第六次为什么可能变慢

发布时间:2026/11/5 21:51:16

PostgreSQL 计划缓存实战(第 10 篇):预编译 SQL 前五次都快,第六次为什么可能变慢 普通租户只有百条订单头部租户却有九十万条。相同 prepared statement 前几次都快某个连接复用后头部租户突然用索引扫描九十万行重连又恢复。直接答案是这个会话可能在累计五个 custom plan 后开始评估 generic plan而 generic plan 看不到本次参数冷热租户差异便被平均分布掩盖。Custom plan 看得到本次参数generic plan 省掉重复规划却只能按平均分布估算。参数倾斜越强省下的规划毫秒越可能换来执行秒级退化。先纠正“第六次必切换”plan_cache_mode auto时PostgreSQL 对带参数的 prepared statement 前五次使用 custom plan并计算其平均估算成本之后生成 generic plan将其成本与平均 custom 成本比较。如果重复规划看起来不值得后续才使用 generic plan。因此第六次是开始具备选择 generic 的条件不是必然切换最终可能继续 customDDL、统计更新等会触发重新分析/规划每个数据库会话拥有自己的 prepared statement 与计数历史。源码证明比较的不是前五次真实耗时PostgreSQL 18.6 的核心入口位于src/backend/utils/cache/plancache.c的choose_custom_plan()。去掉强制模式、一次性计划等前置分支后决策等价于# 逻辑等价伪代码不是可编译源码 num_custom_plans 5 → custom avg_custom_cost total_custom_cost / num_custom_plans generic_cost avg_custom_cost → generic 其他情况 → custom这里有两个容易写错的细节比较的是 planner 的估算成本不是前五次的真实执行时间custom cost 会计入重复规划的估算开销generic cost 不计每次重规划因此 generic 只要在“执行估算 重规划代价”的总账上更便宜就可能胜出。第一次尝试 generic 时generic_cost尚未确定代码会先构建 generic plan、记录成本再调用一次choose_custom_plan()复核如果 custom 一直明显占优这个刚生成的 generic plan 不会被实际执行。因此“第六次开始评估”比“第六次执行 generic”更准确。PREPARE/EXECUTE → GetCachedPlan() → choose_custom_plan() → num_custom_plans、total_custom_cost、generic_cost → custom 或 generic → pg_prepared_statements.generic_plans/custom_plans本文不补无关 Java 示例要证明的是 PostgreSQL 服务端计划缓存决策SQLPREPARE/EXECUTE是最直接入口。JDBC 驱动的 server-side prepare 阈值属于调用前置条件必须在生产排查时另行确认不能代替服务端证据。构造冷热租户DROPTABLEIFEXISTStenant_order;CREATETABLEtenant_order(idbigintPRIMARYKEY,tenant_idbigintNOTNULL,payloadtextNOTNULL);-- 热租户 1900000 行INSERTINTOtenant_orderSELECTg,1,repeat(h,100)FROMgenerate_series(1,900000)ASg;-- 1000 个冷租户每个约 100 行INSERTINTOtenant_orderSELECT900000g,2((g-1)%1000),repeat(c,100)FROMgenerate_series(1,100000)ASg;CREATEINDEXtenant_order_tenant_idxONtenant_order(tenant_id);ANALYZEtenant_order;PREPAREtenant_q(bigint)ASSELECTsum(length(payload))FROMtenant_orderWHEREtenant_id$1;先做确定性对照SETplan_cache_modeforce_custom_plan;EXPLAIN(ANALYZE,BUFFERS,SETTINGS)EXECUTEtenant_q(2);EXPLAIN(ANALYZE,BUFFERS,SETTINGS)EXECUTEtenant_q(1);SETplan_cache_modeforce_generic_plan;EXPLAIN(ANALYZE,BUFFERS,SETTINGS)EXECUTEtenant_q(2);EXPLAIN(ANALYZE,BUFFERS,SETTINGS)EXECUTEtenant_q(1);Custom plan 对冷租户通常适合索引路径对 90% 行都命中的热租户可能选择顺序扫描。Generic plan 中会保留$1无法知道这次是头部租户可能按平均每租户行数选择索引路径导致热参数大量随机/重复 heap 访问。实际计划取决于缓存、成本参数和数据宽度。这个实验的判定不是强求某个节点而是比较同一参数在 custom/generic 下的行数估算、Buffers 与耗时。恢复自动模式重新准备以清空该语句的计划历史DEALLOCATEtenant_q;SETplan_cache_modeauto;PREPAREtenant_q(bigint)ASSELECTsum(length(payload))FROMtenant_orderWHEREtenant_id$1;EXECUTEtenant_q(2);EXECUTEtenant_q(3);EXECUTEtenant_q(4);EXECUTEtenant_q(5);EXECUTEtenant_q(6);EXPLAIN(ANALYZE,BUFFERS,SETTINGS)EXECUTEtenant_q(1);SELECTname,generic_plans,custom_plans,statementFROMpg_prepared_statementsWHEREnametenant_q;前五次全是冷租户会影响平均 custom 成本第六次热参数可能遇到 generic也可能算法仍判定 custom 更好。generic_plans/custom_plans是事实证据不能仅凭“恰好第六次慢”倒推。还应记录同一次观察前后的计数差而不是只看最终总数SELECTname,generic_plans,custom_plansFROMpg_prepared_statementsWHEREnametenant_q;EXPLAIN(ANALYZE,BUFFERS,SETTINGS)EXECUTEtenant_q(1);SELECTname,generic_plans,custom_plansFROMpg_prepared_statementsWHEREnametenant_q;若generic_plans增加说明这次取得了 generic plan若只看到$1也应与计数交叉验证避免把展示差异当成完整会话历史。为什么连接池让故障像随机事件Prepared statement 是 session 对象。连接池中的每个后端经历不同参数序列连接 A冷、冷、冷、冷、冷 → generic → 热参数退化 连接 B热、热、冷、热、冷 → custom 平均成本不同 连接 C刚重建连接 → 重新从 custom 开始应用日志看到同一 SQL数据库看到的是多份会话级计划历史。pg_prepared_statements也只显示当前会话可见对象不能从一个管理连接观察整个池。还要确认驱动是否真的创建 server-side prepared statement、准备阈值是多少、事务池化是否保留 session以及代理是否改写连接语义。客户端叫“预编译”不自动等于 PostgreSQLPREPARE。四种处理路径路径适用条件代价与边界保持auto参数分布较均匀规划成本值得节省极端热点可能被平均值掩盖局部force_custom_plan参数强烈决定路径、单次执行较重每次支付规划 CPU拆分冷热 SQL/连接热租户可稳定识别应用路由与观测复杂度上升索引、分区或模型治理倾斜是长期业务事实变更成本高但可能根治访问路径不要全局强制 custom。高 QPS、执行极短的语句可能把大量 CPU 浪费在重复规划也不要用DEALLOCATE或重连作为长期修复它只重置历史退化可能再次出现。生产证据链在同一会话、同一参数下分别强制 custom/generic比较计划与执行。看$1是否仍出现在计划中结合generic_plans/custom_plans确认类型。找第一处 estimated/actual rows 分叉确认租户热点是否进入统计。核对驱动、连接池、代理和 prepared threshold。用pg_stat_statements比较 calls、planning/execution time 与波动但注意它不会直接替代会话级计划证据。在真实参数分布和并发下计算“规划 CPU 执行成本”的总账。如果第一处分叉来自列相关性或数据分布估错先阅读第 9 篇SQL 和索引没变计划为什么突然慢一百倍修复统计证据计划缓存不能替代基数估算治理。灰度与回滚优先对专用报表角色、事务或单个连接设置BEGIN;SETLOCALplan_cache_modeforce_custom_plan;-- 目标查询COMMIT;灰度记录热/冷参数 p95、规划 CPU、数据库总 CPU 和连接池吞吐。若规划时间或整体 CPU 超过停止阈值停止扩大范围。恢复auto可撤销设置但不能自动修复数据倾斜和索引模型。证据边界证据能证明不能证明第六次变慢与启发式时点吻合一定已经切 generic计划中出现$1当前展示的是 generic plan所有连接都使用 generic强制 custom 更快参数感知对该值有收益全局 custom 总成本更低重连恢复session 状态参与故障根因只有 plan cache统计显示热点planner 有热点信息generic 能使用本次参数值面试表达主线Prepared statement 可以用参数感知 custom plan也可复用 generic plan。Auto 前五次采样 custom 成本之后比较 generic 与平均 custom 成本并非第六次必切。参数倾斜时要在同一会话对比两类计划并结合驱动、连接池和规划 CPU 做局部治理。实验清理DEALLOCATEtenant_q;RESET plan_cache_mode;DROPTABLEIFEXISTStenant_order;官方资料PostgreSQL 18PREPAREPostgreSQL 18plan_cache_modePostgreSQL 18pg_prepared_statementsPostgreSQL 18EXPLAINPostgreSQL 18.6 源码plancache.c / choose_custom_plan()PostgreSQL 18.6 源码标签 REL_18_6
延伸阅读

更多相关文章

2026/11/4 22:24:26

快速舞蹈视频补帧全指南:从FFmpeg到RIFE实战优化

之前在整理一段节奏很快的舞蹈素材时,我遇到一个很典型的困惑:原视频帧率只有 30fps,角色或舞者一旦快速下叉、甩腿、左右摆动,画面就会明显发虚,甚至能感觉到“一卡一卡”的拖影。后面尝试用补帧(Frame In…

2026/11/5 21:51:16

端侧AI的价值真相:从模型效率到硬件部署的工程挑战

端侧AI到底有没有价值,这个问题的答案,正在从“技术趋势”变成“资本问题”。面壁智能冲刺上市,让这件事变得更有意思:当一家以“小模型高效率”为路线的AI公司走进二级市场视野,它真正需要回答的,不是“参…

2026/10/31 19:40:08

手机变身探测器:SweepLED用AI分析反光定位隐藏摄像头

如果你需要在房间里快速排查隐藏摄像头,SweepLED 是一个很值得关注的检测思路:它来自 KAIST 相关研究团队,核心做法是把智能手机自带的屏幕光源变成主动照明设备,再利用 AI 分析摄像头画面中的反光特征,帮你定位可疑的…

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑