先验证是否为参数嗅探:查执行计划中parametercompiledvalue与parameterruntimevalue是否错位,且执行计划出现索引扫描代替查找;再查query_stats中last_elapsed_time显著高于均值。

怎么确认真是参数嗅探在作怪
别一慢就加 OPTION (RECOMPILE),先验证是不是它。关键看编译值和运行值是否错位:查 sys.dm_exec_query_plan 提取 XML 执行计划,搜索 <parameterlist></parameterlist> 节点,对比 ParameterCompiledValue 和 ParameterRuntimeValue。如果前者是 '2020-01-01'、后者是 '2025-09-28',且执行计划里该走索引查找却用了扫描,基本坐实。
顺手跑下这个语句,看历史波动:
SELECT t.text, qs.avg_elapsed_time, qs.last_elapsed_time FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t WHERE t.text LIKE '%your_proc_name%' AND qs.last_elapsed_time > qs.avg_elapsed_time * 3;
结果里 last_elapsed_time 显著高于均值,就是典型参数嗅探信号。
用局部变量隔离是最轻量的解法
对大多数中等分布的参数(比如客户ID、状态码),直接声明同类型局部变量赋值,就能让优化器“看不见”真实参数值,转而依赖统计信息估算——既避免重编译开销,又比通用计划更贴近实际。
写法很简单:
CREATE PROC dbo.GetOrders @cust_id INT AS BEGIN DECLARE @cust_id_local INT = @cust_id; -- 关键这行 SELECT order_id, amount FROM Orders WHERE customer_id = @cust_id_local; -- 后续全用这个变量 END
注意三点:
- 变量必须在存储过程主体内声明,不能在
IF分支里临时声明再用 - 不能用于
IN子查询或EXEC(@sql)动态拼接场景 - 如果参数本身是
NULL或空字符串这类统计直方图里单独成桶的值,效果会打折扣
什么时候该上 OPTION (RECOMPILE)
OPTION (RECOMPILE) 不是加速器,是“急救包”——只在参数取值跨度极大、且 WHERE 条件完全由它主导时才值得用。比如时间范围从 1 天到 5 年、客户数据量从 10 行到 500 万行。
但必须警惕副作用:
- 高频调用(如每秒 20+ 次)会导致 CPU 持续升高,
sys.dm_exec_query_stats里plan_generation_num会频繁跳变 - 无法响应后续统计信息更新,哪怕你刚跑了
UPDATE STATISTICS,它下次还是重新编译,不复用新统计 - 别给整个存储过程加
WITH RECOMPILE,粒度太粗;优先只加在卡顿最狠的那条SELECT或UPDATE末尾
示例:
SELECT * FROM Sales WHERE order_date >= @start_date AND order_date <h3>OPTIMIZE FOR UNKNOWN 更适合稳定 OLTP 场景</h3><p>如果你的存储过程每分钟被调用几十次,且参数虽有变化但没极端倾斜(比如订单状态在 <code>'Pending'</code>/<code>'Shipped'</code>/<code>'Cancelled'</code> 间均匀分布),<code>OPTION (OPTIMIZE FOR UNKNOWN)</code> 是比 <code>RECOMPILE</code> 更稳的选择。</p><p>它让优化器彻底忽略参数值,纯靠列统计信息的平均密度估算行数,生成一个“中庸但可靠”的计划。缺点是:遇到真实数据分布严重偏斜(比如 99% 是 <code>'Active'</code>,1% 是 <code>'Archived'</code>),可能不如 <code>RECOMPILE</code> 精准。</p><p>验证方式很直接:开启 <code>SET STATISTICS XML ON</code>,看执行计划里 <code>Estimated Number of Rows</code> 是否落在业务常见量级附近。如果加了提示后估算值从 1 跳到 50 万,而你日常查的客户通常有 10–20 万订单,那就对了。</p><p>容易踩的坑是写成 <code>OPTIMIZE FOR (@p = NULL)</code>——NULL 在直方图里是独立桶,常导致全表扫描;真要覆盖空值场景,得配合 <code>OR @p IS NULL</code> 逻辑,或改用局部变量方案。</p>











