sql server 2022的psp是自动启用的机制,通过缓存中存在多个含parametersensitiveplan节点的plan_handle且execution_count分布不均来确认;optimize for unknown、recompile、隐式转换、表提示等会使其失效,依赖准确统计信息直方图识别参数敏感性。

SQL Server 2022 的参数敏感计划(PSP)不是开关式功能,而是优化器在满足条件时自动启用的机制;它不依赖你改存储过程代码,但对统计信息、查询结构和提示语句极其敏感——用错一个 OPTIMIZE FOR UNKNOWN 就会让整个 PSP 失效。
怎么确认你的存储过程正在用 PSP?
关键不是看有没有“开启”,而是查缓存里是否生成了多个计划变体。PSP 不会显式报“已启用”,只通过行为暴露:
- 执行
SELECT p.*, q.query_plan FROM sys.dm_exec_procedure_stats p CROSS APPLY sys.dm_exec_query_plan(p.plan_handle) q WHERE object_id = OBJECT_ID('YourProc'),如果同一存储过程出现多个不同plan_handle,且query_plan中含ParameterSensitivePlan或SchedulerExpression节点,说明 PSP 已介入 - 检查
sys.dm_exec_query_stats中相同sql_handle是否对应多个plan_handle,且execution_count分布不均(比如某 plan 执行 100 次,另一 plan 执行 800 次),这是 PSP 根据参数值分流的典型痕迹 - 避免被误导:仅靠
sys.dm_exec_cached_plans查usecounts不够,因为 PSP 的调度计划(scheduler plan)本身也占一个缓存项,但它不直接执行,只是路由入口
哪些写法会让 PSP 直接失效?
PSP 是“默认尝试启用 + 显式禁用即退场”的设计,以下任意一条都会让优化器放弃 PSP 路径:
- 存储过程中用了
OPTION (OPTIMIZE FOR UNKNOWN):这个提示明确告诉优化器“别看参数值”,PSP 失去感知基础 - 加了
OPTION (RECOMPILE):每次都要重编译,绕过计划缓存,自然也绕过了 PSP 的多计划缓存机制 - 谓词中存在隐式转换,比如
WHERE CustomerID = @id但@id是varchar而列是int:这会破坏统计信息直方图的基数估算精度,PSP 无法可靠识别“高/低边界值” - 查询中使用了
NOEXPAND或表提示如WITH (INDEX(...)):这些硬性约束会干扰优化器对谓词敏感度的评估逻辑
为什么统计信息更新比加索引还关键?
PSP 的核心判断依据是列统计信息直方图中的“非均匀分布”。没有准确的直方图,它根本识别不出哪些参数值会导致行数从 5 行跳到 50000 行:
- 必须确保相关列(尤其是 WHERE 条件里的参数化列)的统计信息是
WITH FULLSCAN或至少WITH SAMPLE更新过的;sp_updatestats默认采样可能漏掉倾斜峰值 - 若表有分区或数据按时间递增写入,旧统计信息中最新分区的直方图桶(step)可能为空或过时,PSP 会误判为“均匀分布”,跳过优化
- 执行
DBCC SHOW_STATISTICS ('Orders', 'IX_Orders_StatusID'),重点看Steps表中EQ_ROWS和RANGE_ROWS是否呈现明显跳跃(比如 step 1: 2 行,step 5: 48000 行),这才是 PSP 触发的信号源
参数值范围怎么影响 PSP 的分组效果?
PSP 不是按具体值匹配,而是把运行时参数映射到统计直方图的“范围桶(bucket)”里。同一个桶内的所有值,共享一个查询变体计划:
- 假设
StatusID统计直方图有 5 个 step,PSP 最多选前 3 个风险最高的谓词,每个谓词生成最多 3 个分组(低/中/高),所以理论上单个查询最多缓存 9 个变体计划,但实际受query_store配置和内存限制 - 如果参数值落在两个直方图 step 的交界处(比如 step 边界是 100 和 200,而传入 199),PSP 可能因插值误差归入错误分组,导致计划次优——这不是 bug,是直方图固有粒度限制
- 不要试图用
OPTIMIZE FOR (@p = 199)强制固定计划:这会压制 PSP 的动态选择能力,反而让后续传入 201 的请求复用 199 的计划,失去适配意义
PSP 的真正复杂点不在开启方式,而在于它完全依赖统计信息质量与谓词可评估性;一旦直方图不准或参数被包装进函数(如 WHERE YEAR(OrderDate) = @year),PSP 就会静默退场——你不会收到任何警告,只会发现执行计划又变“一刀切”了。











