当前 plan cache hit rate 应通过性能计数器 sqlserver:plan cache\cache hit ratio 查看,t-sql 可查 sys.dm_os_performance_counters 计算百分比;低于 80% 才需关注,且命中率高不等于性能好,参数嗅探可能导致缓存命中但计划低效。

怎么看当前 plan cache hit rate?
SQL Server 的执行计划缓存命中率不是默认暴露的指标,得自己算。它本质是缓存复用次数占总编译/重编译请求的比例,不是“用了多少内存”或“缓存里有多少计划”。直接查 sys.dm_exec_plan_attributes 或 sys.dm_exec_cached_plans 都不直观,真正靠谱的是从性能计数器入手。
推荐做法是查 Windows 性能计数器:SQLServer:Plan Cache\Cache Hit Ratio,对应实例级整体命中率。如果用 T-SQL 监控(比如集成进巡检脚本),就该读 sys.dm_os_performance_counters:
SELECT cntr_value * 100.0 / NULLIF((SELECT cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Cache Pages'
AND object_name LIKE '%Plan Cache%'), 0) AS hit_rate_pct
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Cache Hit Ratio'
AND object_name LIKE '%Plan Cache%';注意:这个值只反映最近一次采样窗口内的趋势,波动大很正常;低于 80% 才真值得盯,别一看到 92% 就松口气——可能刚跑完一批参数化良好的查询,掩盖了后续高频重编译问题。
为什么参数嗅探会让 plan cache hit rate 失真?
参数嗅探本身不降低命中率,但它让“命中”变得没意义:同一个 sp_executesql 语句,因首次传入的参数值不同,生成了低效计划并缓存,后续调用即使参数更合理,也照样复用这个烂计划——缓存是 hit 了,性能却崩了。
典型症状包括:
- 执行时间忽长忽短,但
sys.dm_exec_query_stats显示execution_count高、total_elapsed_time却严重倾斜 -
sys.dm_exec_cached_plans里同个plan_handle对应多个不同sql_handle(说明被不同参数触发过多次编译) - 强制加
OPTION (RECOMPILE)后变快,但命中率数字反而掉——因为绕过了缓存
根本矛盾在于:SQL Server 为“第一次参数”优化,而业务请求的参数分布往往偏态。比如查“订单状态=已发货”占 95%,但首次执行传的是“订单状态=待审核”,结果缓存了个全表扫描计划。
怎么快速识别和缓解参数嗅探导致的坏计划?
别等用户报慢才查。在关键存储过程或高频 sp_executesql 调用前,加轻量级检测逻辑:
实操建议:
- 对参数值做简单分类:比如
@status是枚举值,就在 proc 开头用CASE WHEN @status IN ('待审核','已取消') THEN ... ELSE ... END分支,各分支内加OPTION (OPTIMIZE FOR (@status = '已发货')) - 用
OPTIMIZE FOR UNKNOWN替代盲目加RECOMPILE,它让优化器按统计信息平均值估算,不依赖首次参数,且仍可缓存计划 - 避免在 where 条件里对参数做函数处理,比如
WHERE YEAR(OrderDate) = YEAR(@date),这直接禁用参数嗅探且无法走索引 - 检查统计信息是否陈旧:
DBCC SHOW_STATISTICS ('Orders', 'IX_OrderStatus')看rows_sampled和modification_counter,自动更新没开或大表更新频繁时容易失效
注意:OPTIMIZE FOR UNKNOWN 在 SQL Server 2008+ 支持,但对含复杂 JOIN 或子查询的语句,可能不如显式 OPTIMIZE FOR 稳定。
调整 plan cache 行为要避开哪些坑?
有人想“清空缓存保平安”,但 DBCC FREEPROCCACHE 是实例级核弹:所有计划清零,接下来每条新查询都得硬编译,CPU 瞬间拉满,连接池可能雪崩。
更安全的做法:
- 只清特定计划:
DBCC FREEPROCCACHE(<code>plan_handle),先从sys.dm_exec_cached_plans查出目标plan_handle - 用
ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = OFF关全局嗅探——但这是钝刀子,影响所有查询,且 2016 SP1+ 才支持 - 别碰
optimize for ad hoc workloads这个服务器配置项来“省内存”:它只存 trivial 计划的 stub,对参数化查询无效,反而增加查找开销 - 监控
sys.dm_exec_query_stats里的plan_generation_num > 1,说明同一语句被反复重编译,这才是真问题信号,不是看缓存大小
最常被忽略的一点:应用层拼接 SQL 字符串(尤其用字符串格式化拼 WHERE 条件)会彻底杀死计划复用,哪怕看着像同一语句,空格、大小写、换行差异都会导致新计划生成。这事得前端后端一起卡,不能光靠 DBA 清缓存。










