执行计划重用时参数嗅探失准会直接拖慢查询:首次用极端参数(如@status='archived'仅占0.1%)生成索引查找计划,后续查@status='active'(95%行)仍复用该计划,导致逻辑读飙升、cpu拉满;典型信号是sys.dm_exec_query_stats中execution_count上升但total_logical_reads剧烈波动。

执行计划重用时参数嗅探失准会直接拖慢查询
SQL Server 在首次编译存储过程时,会“嗅探”传入的实际参数值,并据此生成执行计划。这个计划后续被复用——但前提是参数分布稳定。一旦首次传入的是极端值(比如 @status = 'Archived' 只占 0.1% 行),生成的计划可能全是索引查找 + RID Lookup;而后续调用 @status = 'Active'(95% 行)时,仍硬套这个计划,结果逻辑读飙升、CPU 拉满。
这不是缓存坏了,而是缓存“太忠实地复用了错误的策略”。你查 sys.dm_exec_query_stats 会发现 execution_count 往上涨,total_logical_reads 却剧烈波动,这就是典型参数嗅探失准信号。
- 别急着加
OPTION (RECOMPILE)——它让每次执行都重编译,CPU 压力翻倍 - 优先用
OPTIMIZE FOR (@param = 'typical_value'),把常见值钉死 - 对分支多的过程(如
IF @mode IN ('A','B','C')),拆成独立小过程更易命中稳定计划
SET 选项不一致会让两个相同过程互不认领彼此的计划
哪怕两个存储过程一字不差,只要一个连接默认开了 SET ARITHABORT ON,另一个没开,SQL Server 就认为它们是“不同上下文”,各自编译、各自缓存。结果就是:同一个过程反复进缓存,usecounts 始终为 1,看起来快了,其实每次都在重编译。
这个问题在 ORM(如 Entity Framework)里高频出现——它默认发 SET ARITHABORT ON,而 SSMS 默认关着。你用 SSMS 测试很快,上线后变慢,大概率就是这个原因。
- 检查当前会话的 SET 状态:
SELECT SESSIONPROPERTY('ARITHABORT')(返回 1=ON,0=OFF) - 统一客户端行为:EF 可配
CommandBehavior.KeyInfo或改连接字符串加ARITHABORT=true - 避免在过程中动态改 SET,比如
SET ANSI_NULLS OFF后再查表——这会强制重编译整条语句
统计信息更新后旧计划未自动淘汰,继续错用
表数据量翻了 10 倍、新建了覆盖索引、删了低效索引……这些变更都会让旧执行计划变得低效。SQL Server 确实有“自动重编译”机制,但它不是实时触发,而是等下次执行时检测到元数据变化才动作。中间空窗期,旧计划还在跑,性能就断崖下跌。
一款AI工具,主要用于管理 OpenClaw 所使用的来自 OpenRouter 的免费 AI 模型。自动按质量对模型进行排序,配置回退机制以应对速率限制,并更新 opencla...,适合需要提升相关任务效率的用户。
你查 sys.dm_exec_query_stats 的 plan_generation_num,如果大于 1,说明已发生重编译;但如果它一直是 1,而性能却突然变差,那大概率是统计信息变了但计划没刷新——得手动干预。
- 确认统计信息是否过期:
DBCC SHOW_STATISTICS('Orders', 'IX_OrderDate')看Modification Counter是否远超行数 20% - 强制更新:
UPDATE STATISTICS Orders WITH FULLSCAN, NORECOMPUTE(慎用 FULLSCAN) - 对大表,用采样更新更稳妥:
UPDATE STATISTICS Orders IX_OrderDate WITH SAMPLE 30 PERCENT
动态 SQL(EXEC / sp_executesql 拼接)彻底绕过计划缓存
只要过程里写了 EXEC(@sql) 或 sp_executesql N'... WHERE '+@condition,哪怕只拼一个字段名或表名,SQL Server 就无法生成稳定哈希值,导致每次执行都走即席编译路径。此时所谓“存储过程优势”完全归零,还多了一层解析开销。
注意:用 sp_executesql 传参是安全的(如 N'SELECT * FROM t WHERE id = @id'),但只要字符串里含变量拼接,就失效。
- 禁止在 WHERE 中拼列名:
'AND '+@col+' = '+@val→ 改用 CASE 或拆过程 - 禁止拼表名:
'SELECT * FROM '+@tbl→ 改用临时表 + 动态建索引,或用视图+SCHEMABINDING - 真要动态,至少保证 SQL 文本完全一致:把所有拼接逻辑移到应用层,数据库只收干净参数
真正难缠的不是“计划没缓存”,而是“计划缓存了,但缓存的是个坏主意”。监控不能只看耗时,得盯住 plan_generation_num、usecounts、total_worker_time 这几个字段的组合变化——否则你优化的只是表象,不是根因。










