参数嗅探是sql server为节省编译开销而设计的默认行为,并非bug;其本质是首次执行时依据参数值生成并缓存执行计划,后续调用直接复用,导致参数分布差异大时性能骤降。

参数嗅探不是 bug,是 SQL Server 为省编译开销做的默认行为;但用 WITH RECURSIVE 完全解决不了它——因为 SQL Server 根本不支持 WITH RECURSIVE 语法。
SQL Server 里根本没有 WITH RECURSIVE
这是个常见误解。PostgreSQL、SQLite、MySQL 8.0+ 支持 WITH RECURSIVE,但 SQL Server 只支持 WITH + CTE(公共表表达式),且递归必须显式用 UNION ALL 和终止条件写,不带 RECURSIVE 关键字。你在 SQL Server 里写 WITH RECURSIVE 会直接报错:Incorrect syntax near the keyword 'RECURSIVE'。别被其他数据库的文档带偏了。
参数嗅探真实触发点:首次执行时的参数值决定缓存计划
存储过程第一次运行,SQL Server 拿到你传的 @Param 值,查统计信息,生成一个“看起来最优”的执行计划并缓存。后续调用不管传什么,只要没重编译,就复用这个计划——哪怕新参数只返回 1 行,而缓存计划是按 10 万行设计的(比如走索引扫描而非查找)。
- 典型现象:
EXEC YourSP @Param = 1很快,EXEC YourSP @Param = 999999卡死几十秒 - 验证方式:对比两次执行的
sys.dm_exec_query_plan,看<parametercompiledvalue></parametercompiledvalue>是否不同 - 注意:改名新建存储过程(如
YourSP_v2)不会绕过问题——只要缓存里已有旧计划,新过程首次执行仍可能踩坑
真正有效的三个实操方案
别折腾不存在的 WITH RECURSIVE,盯住这三个经生产验证的解法:
-
局部变量屏蔽:在过程体开头声明
DECLARE @LocalParam INT = @Param,后面所有查询都用@LocalParam。优化器失去具体值,转而依赖列统计密度估算,计划更中性 -
OPTION (RECOMPILE):加在慢查询语句末尾,每次执行都重编译。适合调用频次低、或参数差异极大(如分页OFFSET值跨度大)的场景 -
OPTION (OPTIMIZE FOR UNKNOWN):让优化器忽略本次参数值,按统计分布平均值估算。比RECOMPILE轻量,但不如局部变量稳定;注意它不能用于INSERT/UPDATE/DELETE的子句中
最容易被忽略的细节
很多人试了局部变量还是慢,往往卡在两个地方:
- 变量类型不一致:比如
@Param VARCHAR(50)赋给@LocalParam VARCHAR(20),隐式转换导致索引失效 - 嵌套调用没处理:A 存储过程调 B,B 用了局部变量,但 A 传参时用了表达式(如
EXEC B @X = LEN(@Y)),B 内部的变量仍可能被“穿透嗅探”










