位置:首页 > SQL > SQL Server 2022/2024 存储过程参数嗅探怎么优化:从 PSP 触发条件到 RECOMPILE 取舍

SQL Server 2022/2024 存储过程参数嗅探怎么优化:从 PSP 触发条件到 RECOMPILE 取舍

时间:2026-08-25  |  作者:宇宙开黑者  |  阅读:0

目录

  1. 怎么确认查询是否真正启用了参数敏感计划
  2. 为什么开了 PSP 还是只有单计划
  3. 局部变量怎么和 PSP 配合,才能更稳地压住嗅探问题
  4. OPTION (RECOMPILE) 还要不要用,应该加在哪里
  5. 混合场景下怎么判断该拆语句还是换策略

前言

参数嗅探并不是 SQL Server 里一个能靠“关掉就完事”的问题。到了 SQL Server 2024,参数敏感计划(PSP)开始能自动分流不同参数值,但它只在满足特定条件时才会生效。本文按“先确认是否触发,再判断为何失效,最后决定用局部变量还是 RECOMPILE”的顺序梳理一套可落地的排查和优化方法。

参数嗅探一直是 SQL Server 存储过程里最容易引发“同一条 SQL,时快时慢”的问题之一。到了 SQL Server 2024,参数敏感计划(PSP)开始能为同一查询保留多种执行计划,但前提并不宽松。想真正把这个能力用起来,关键不在于记住一个开关,而是先判断查询是否满足触发条件,再决定该用 PSP、局部变量,还是只对个别慢语句加 OPTION (RECOMPILE)

怎么确认查询是否真正启用了参数敏感计划

不是所有带参数的查询都能自动获得多套执行计划。SQL Server 2024 中,PSP 只有在满足一组前提时才会介入:

PSP 启用条件与检查路径信息图
PSP 触发条件与验证路径用条件清单和检查路径展示 SQL Server 2024 中 PSP 是否会真正接管查询计划。
  • 列统计信息的直方图必须体现非均匀分布。例如某字段中 95% 的值是 'A',其余 5% 才分布在 'B''Z',这类明显倾斜的数据更容易触发 PSP。
  • 优化器最多只会对 前三个谓词做 PSP 评估。也就是说,WHERE 里即便有多个 @param = col 条件,真正参与判断的通常也只是最敏感的三个。
  • 必须生成完全优化的计划,也就是执行计划中能看到 StatementOptmLevel="FULL"。如果语句用了 OPTION (RECOMPILE),或者属于分布式查询,就不参与 PSP。
  • 查询存储必须启用,且数据库的 QUERY_STORE 需要处于 READ_WRITE 模式。
  • 数据库兼容级别还要满足 >= 160,否则 PSP 根本不会启用。

确认方式不要只看“理论上支持”,最好直接看执行结果。执行目标慢查询后,可以先查 sys.query_store_plan,观察 is_forced_planquery_plan_hash 是否出现多个不同哈希值;再结合 sys.dm_exec_query_stats,检查是否存在相同 sql_handle、但对应多个不同 plan_handle 的记录。

如果同一条参数化查询始终只有一套计划,基本就说明 PSP 没有接管,后面就需要继续排查触发条件是否被卡住。

为什么开了 PSP 还是只有单计划

很多人以为升级版本、打开功能后,参数嗅探问题就会自动缓解,实际并不是这样。PSP 失效往往发生在几个很具体的地方。

参数定义不一致,会直接破坏计划比较基础

如果同一个 @param 每次调用时类型或长度不一致,例如一次传 nvarchar(10),另一次传 nvarchar(50),计划缓存就可能被拆散。这样一来,优化器拿不到稳定的比较对象,PSP 很难对同一查询形成多变体计划。

谓词里混用参数和字面量,可能让 PSP 识别失败

像下面这种写法就要小心:

局部变量与 RECOMPILE 选型对比信息图
局部变量、PSP 与 RECOMPILE 的把局部变量、PSP 和 OPTION (RECOMPILE) 的适用边界放在同一张图里。
WHERE Company = @Company AND Status = 'Active'

当条件中一部分是参数、一部分是固定字面量时,谓词独立性会变差,PSP 未必能按预期识别出真正的参数敏感点。问题不一定每次都暴露,但在复杂查询里,这类写法确实容易降低可预测性。

统计信息不准,优化器就无法判断值分布

PSP 依赖统计信息去区分“哪些参数值会返回很少数据,哪些会触发大结果集”。如果 UPDATE STATISTICS 没有及时运行,或者采样率太低,直方图失真后,优化器就很难做出多计划分流。

这时可以直接在 SSMS 里执行:

DBCC SHOW_STATISTICS('YourTable', 'YourIndex')

重点看 Histogram 是否足够细,通常可以关注步骤数是否达到 >= 200,以及 AVG_RANGE_ROWS 是否存在明显跳变,例如从 1 直接跳到 10000。这类差异越明显,越说明数据分布倾斜,PSP 才更有发挥空间。

兼容级别不够,功能根本不会工作

如果数据库兼容级别低于 160,即便 SQL Server 版本已经更新,也不能指望 PSP 正常生效。这是最基础、也最容易被忽略的一项检查。

局部变量怎么和 PSP 配合,才能更稳地压住嗅探问题

只依赖 PSP 往往有盲区,尤其是在参数模式复杂、调用方式不一致的存储过程里。更稳妥的思路,是用局部变量先把关键参数接住,再让优化器基于更稳定的表达式去生成计划。

这里有几个细节不能写错:

  • 声明和赋值最好放在同一行,例如:DECLARE @LocalCompany NVARCHAR(100) = @Company;。如果拆成 DECLARESET,有些场景下可能被优化器绕过,起不到预期隔离效果。
  • 类型和长度必须严格一致。若 @CompanyNVARCHAR(50),局部变量就不要写成 NVARCHAR(20),否则隐式转换会影响索引使用,最后问题从参数嗅探变成索引失效。
  • 只要是关键过滤条件,就要一起处理。假设 WHERE 里既有 @Company 又有 @Region,那两个都应该映射到局部变量,漏掉一个,嗅探仍可能从剩余参数穿透进来。
  • 局部变量并不会天然阻止 PSP。它只是把“直接基于输入参数编译”的不确定性,转移到局部变量层,PSP 仍可能基于 @LocalCompany 这样的运行时值去选择计划变体。

更稳妥的结构可以写成这样:

CREATE PROCEDURE sp_get_sales 
@Company NVARCHAR(100), 
@Region CHAR(2)
AS
BEGIN
DECLARE @LocalCompany NVARCHAR(100) = @Company;
DECLARE @LocalRegion CHAR(2) = @Region;

SELECT SUM(SalesAmount) 
FROM SalesTable 
WHERE Company = @LocalCompany 
AND Region = @LocalRegion
OPTION (OPTIMIZE FOR (@LocalCompany UNKNOWN)); -- 可选:给 PSP 加一层提示

END

这里的 OPTION (OPTIMIZE FOR (@LocalCompany UNKNOWN)) 不是必选项,但在某些分布不够稳定的场景下,可以作为额外提示,帮助优化器避免过度依赖某一次参数值。

OPTION (RECOMPILE) 还要不要用,应该加在哪里

到了 2024 年,PSP 并没有让 OPTION (RECOMPILE) 过时,只是把它的使用范围压缩到了更明确的场景里。

只给具体慢语句加,不要给整个过程加

正确思路是把重编译限制在真正有问题的那条语句末尾,例如:

SELECT ... WHERE id = @x OPTION (RECOMPILE);

这样做的目标很明确:只为这一句重新编译,不把整个存储过程都拖下水。

适合低频、跨度极大的参数场景

如果参数波动非常夸张,但执行频率并不高,OPTION (RECOMPILE) 仍然是更直接的办法。典型例子就是分页:

OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY

@skip 从 10 跳到 1000000 时,单一计划往往很难兼顾,临时重编译反而更容易拿到合适方案。

别只重编译子查询,漏掉主 DML

INSERTUPDATE 这类语句里,有些人会把 OPTION (RECOMPILE) 加在内部子查询上,却没有处理主语句本身。结果就是子查询重编译了,外层 DML 仍旧沿用旧计划,性能改善非常有限。

避免用 WITH RECOMPILE 创建整个过程

不要轻易用 WITH RECOMPILE 来创建存储过程。这样会让整个过程每次执行都重编译,CPU 开销通常比单条语句重编译高得多,代价并不小。

还要注意一个容易被忽略的事实:PSPOPTION (RECOMPILE) 是互斥的。只要加了后者,前者就不会生效,因此不要把两者叠在同一条语句上。

混合场景下怎么判断该拆语句还是换策略

实际最棘手的,并不是“PSP 和 RECOMPILE 选哪个”这么简单,而是一个存储过程中常常同时存在两类完全不同的查询。

例如,一部分语句是高频、小结果集查询,更适合依赖 PSP 维护多套计划;另一部分语句则是低频、可能触发大范围扫描的重型查询,这时反而更适合单独使用 OPTION (RECOMPILE)。如果把它们硬塞进同一策略里,通常谁都照顾不好。

更可执行的做法,是把这些语句拆开看待,按语句级别决定策略,而不是给整个存储过程贴一个统一标签。判断依据也不复杂,先监控 sys.dm_exec_query_stats 中各语句的 last_logical_readslast_elapsed_time,把真正波动大的语句筛出来,再决定是保留 PSP、改写为局部变量模式,还是对个别语句直接重编译。

换句话说,参数嗅探优化从来不是“选一个银弹”就结束。SQL Server 2024 的 PSP 确实让问题比过去更容易处理,但它仍然高度依赖统计信息、兼容级别、参数写法和语句结构。对 SQL Server 2022/2024 环境来说,最稳妥的路线仍是先验证 PSP 是否真的生效,再按语句粒度补上局部变量或 OPTION (RECOMPILE),而不是把希望押在某一个开关上。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多