SQL Server单查询存1531个执行计划,sp_BlitzCache未检测到该如何排查?
遇到这种情况确实挺头疼的——明明sp_Blitz提示有查询攒了上千个执行计划,但sp_BlitzCache却没揪出来。我分享几个实用的方法帮你定位:
1. 确认计划缓存的时效性
sp_Blitz的提示是基于它执行时的计划缓存状态,但计划缓存可能因为内存压力、手动清理或者SQL Server自动老化机制被清空。建议立刻重新运行sp_Blitz确认那个多计划提示是否还存在,紧接着执行sp_BlitzCache @ExpertMode = 1,避免时间差导致数据不一致。
2. 用系统视图手动查询核心信息
sp_BlitzCache本质也是封装了系统视图,但可能存在过滤逻辑遗漏的情况。直接查询sys.dm_exec_query_stats和sys.dm_exec_sql_text,按query_hash分组(query_hash相同代表逻辑上完全一致的查询),找出计划数异常的查询:
SELECT qs.query_hash, COUNT(DISTINCT qs.plan_handle) AS NumberOfPlans, -- 提取查询核心片段 SUBSTRING(st.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS QueryText, st.text AS FullQueryText FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st GROUP BY qs.query_hash, st.text, qs.statement_start_offset, qs.statement_end_offset HAVING COUNT(DISTINCT qs.plan_handle) > 1 ORDER BY NumberOfPlans DESC;
这个查询会直接列出所有有多个执行计划的查询,按计划数从高到低排序,很容易定位到那个有1531个计划的目标。
3. 排查参数化设置与临时对象影响
多计划问题大多和参数化有关,先检查数据库的参数化级别:
SELECT name, is_parameterization_forced FROM sys.databases WHERE name = '你的目标数据库名';
如果是SIMPLE模式,SQL Server只会对简单查询自动参数化,复杂查询可能生成多个计划。另外,如果查询中用到了临时表或表变量,不同会话的临时对象元数据不同,也会导致计划无法共享,这种情况sp_BlitzCache可能不会归类到“多计划查询”里,需要结合上面的系统视图查询结果,检查FullQueryText里是否包含#temp或@table。
4. 直接扫描计划缓存找高频计划
用sys.dm_exec_cached_plans直接统计缓存中相同查询的计划数量,适合定位那些因为文本微小差异(比如空格、大小写)导致的多计划:
SELECT cp.cacheobjtype, cp.objtype, COUNT(*) AS PlanCount, -- 取查询前200个字符作为片段,方便识别 SUBSTRING(st.text, 1, 200) AS QueryFragment FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st GROUP BY cp.cacheobjtype, cp.objtype, SUBSTRING(st.text, 1, 200) HAVING COUNT(*) > 100 -- 先筛选计划数超过100的,快速缩小范围 ORDER BY PlanCount DESC;
找到计划数高的QueryFragment后,再结合FullQueryText确认完整查询内容。
5. 调整sp_BlitzCache的参数
试试不带@ExpertMode=1的默认执行sp_BlitzCache,或者增加@TopQueries参数扩大结果范围,比如:
sp_BlitzCache @TopQueries = 2000;
有时候ExpertMode的过滤规则会排除一些边缘情况的多计划查询。
内容的提问来源于stack exchange,提问作者Sicilian-Najdorf

