参数嗅探是sql server默认行为而非bug,应通过局部变量(类型严格匹配)、option(recompile)(仅限慢查询末尾)或optimize for unknown来应对,避免全局禁用或错误加在过程头。

直接结论:参数嗅探不是 bug,是 SQL Server 默认行为;解决它不靠“禁用”,而要选对策略——局部变量最稳,OPTION (RECOMPILE) 最准,OPTIMIZE FOR UNKNOWN 是折中。
为什么存储过程有时快有时慢?
因为 SQL Server 第一次执行时用你传的参数值生成并缓存了执行计划,后续不管传什么,只要没重编译,就硬套这个计划。比如第一次传的是小公司 ID(返回 10 行),优化器选了索引查找;第二次传大公司 ID(返回 50 万行),还走查找,结果触发几十万次随机 IO,卡死。
典型现象包括:
-
EXEC YourSP @id = 1耗时 20ms,EXEC YourSP @id = 999999耗时 48s - 在 SSMS 里粘贴 SQL 手动执行很快,但调用同名存储过程就慢
-
DBCC FREEPROCCACHE后首次变快,过一阵又慢回来
局部变量怎么写才真正生效?
声明和赋值必须一步到位,且类型严格一致——否则隐式转换会让索引失效,等于白干。
正确写法示例:
CREATE PROCEDURE sp_get_sales @Company NVARCHAR(100) AS BEGIN DECLARE @LocalCompany NVARCHAR(100) = @Company; -- 类型、长度完全匹配 SELECT SUM(SalesAmount) FROM SalesTable WHERE Company = @LocalCompany; END
容易踩的坑:
- 变量类型不一致:
@Company VARCHAR(50)赋给@LocalCompany VARCHAR(20),导致截断或隐式转换 - 在嵌套调用中传参用了表达式:
EXEC sp_inner @x = LEN(@y),此时@x仍可能被“穿透嗅探” - WHERE 条件里混用:
WHERE Company = @LocalCompany AND Account = @Account(后者没屏蔽)
OPTION (RECOMPILE) 加在哪、什么时候加?
只加在具体慢查询语句末尾,不是整个存储过程头上。它让这一句每次执行都重编译,其他语句仍可复用计划,开销可控。
适用场景:
- 参数差异极大(如分页
OFFSET从 10 到 1000000) - 执行频次低(每天几次的报表、后台任务)
- 关键路径不能容忍任何计划失配(如财务核对类查询)
错误用法:
- 加在
CREATE PROCEDURE ... WITH RECOMPILE—— 整个过程每次调都重编译,CPU 压力陡增 - 加在
EXEC YourSP WITH RECOMPILE—— 只影响本次,不解决缓存污染问题 - 加在 INSERT/UPDATE 的子查询里却漏掉主语句
最容易被忽略的验证点
改完代码不验证,等于没改。重点看三处:
- 查
sys.dm_exec_cached_plans,确认对应语句的usecounts是否稳定上升(说明计划真被复用了) - 用
SET STATISTICS XML ON对比不同参数的执行计划,检查ParameterCompiledValue和ParameterRuntimeValue是否不再强绑定 - 监控
sys.dm_exec_query_stats中该语句的avg_logical_reads波动是否收窄(目标:波动
类型不一致、嵌套穿透、验证缺位——这三个点,比方案选错更容易让优化失效。











