Chr*_*cht 3 sql-server plan-cache sql-server-2016 sp-blitz
我正在使用Brent Ozar 的sp_Blitz,其结果之一是:
一个查询的多个计划
计划缓存中的单个查询有 1146 个计划 - 这意味着我们可能存在参数化问题。
SELECT q.PlanCount,
q.DistinctPlanCount,
qs.query_hash,
st.text AS QueryText,
qp.query_plan AS QueryPlan
FROM ( SELECT query_hash,
COUNT(DISTINCT(query_hash)) AS DistinctPlanCount,
COUNT(query_hash) AS PlanCount
FROM sys.dm_exec_query_stats
GROUP BY query_hash
) AS q
JOIN sys.dm_exec_query_stats qs ON q.query_hash = qs.query_hash
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp
WHERE PlanCount > 1
ORDER BY q.PlanCount DESC, q.query_hash;
Run Code Online (Sandbox Code Playgroud)
...它显示了计划数量最多的查询。
当我运行它时,我得到的最重要的结果之一(对于相同的查询哈希有大约 1150 个计划)让我感到困惑:
也许在屏幕截图上有点难以识别 -这是一个完整的CREATE PROCEDURE
定义,包括 comments,如下所示:
-- =============================================
-- Author: my username
-- Create date: 14.09.2017
-- Description: blah
-- =============================================
CREATE PROCEDURE [dbo].[spCalcSomeStuff]
@Orders OrderList readonly
AS
BEGIN
-- do stuff (see below for more details what the SP does)
END
Run Code Online (Sandbox Code Playgroud)
另外,它们都是一样的。
我将 QueryText 从多行复制到文本文件中并进行了差异,它们都是 100% 相同的。
知道为什么会这样吗?
我们的数据库对象在源代码控制中,所以我知道这个特定的 SP 上次更改是大约两个月前。即使我们每天多次删除和重新创建它(我们不这样做),我仍然不明白为什么 SQL Server 为相同的查询创建那么多计划。
这个 SP 没有什么特别之处,只是它是我们拥有的极少数使用Table-Valued Parameters 的SP 之一。
这是 SP 功能的简化版本:
create table #tmp
(
[...]
)
insert into #tmp (...)
select ...
from tbOrders o
inner join @Orders x on o.Col1 = x.Col1 and o.Col2 = x.Col2
-- about 15 updates like this one (but more complex),
-- getting stuff from lots of different tables:
update t
set foo = o.foo
from #tmp t
inner join OtherTable o on t.bar = o.bar
-- and a few very simple updates:
update #tmp set ordertype = 'A' where producttype = 4
update #tmp set ordertype = 'B' where producttype = 2
select * from #tmp
Run Code Online (Sandbox Code Playgroud)
当我运行Aaron Bertrand's modified query 时,它会UPDATE
在最后返回两个简单的语句。
即,对于相同的查询哈希,我仍然得到 ~1150 行,其中一半具有以下查询文本:
update #tmp set ordertype = 'A' where producttype = 4
Run Code Online (Sandbox Code Playgroud)
...而其他人有这个:
update #tmp set ordertype = 'B' where producttype = 2
Run Code Online (Sandbox Code Playgroud)
问题 1:“CREATE PROCEDURE 有什么用?!?” 当您执行存储过程时,SQL Server 将存储过程的整个文本存储为您调用的内容。
您不是在创建存储过程——您只是在执行它——但这对于刚开始分析计划缓存的人来说可能有点混乱。
所以,嘿,你现在已经越过了那个障碍!耶,你!
问题 2:“1 个存储过程如何有多个计划?” 没有看到它的全文,很难说,但我会从 Erland Sommarskog 的史诗文章开始,应用程序中的缓慢,SSMS 中的快速。特别是,检查标题为不同设置的不同计划的部分。
我实际上不认为这是问题所在——我打赌 proc 文本中有一些动态的东西——但我知道你不想在这里发布你的确切代码。如果没有看到确切的代码,局外人很难回答这个特定的问题。
更新:谜团解决了。你随便提到这个存储过程恰好使用了表值参数。这是如何调用 TVP 的已知问题。这是一个很好的例子,说明为什么包含您有疑问的完整代码如此重要- 有时即使是最微小的事情也会对您提出的问题产生重大影响。
这并不是整个问题的答案,而是展示了如何获得有关这些行所表示的各个语句的更好的详细信息。目前,查询仅检索过程的整个文本,而不是缩小为该查询统计行收集的过程部分。作为评论,这将是可怕的。
改变:
st.text AS QueryText,
Run Code Online (Sandbox Code Playgroud)
到:
SUBSTRING(st.[text],qs.statement_start_offset/2,
(CASE WHEN qs.statement_end_offset = -1
THEN LEN(CONVERT(nvarchar(max), st.[text])) * 2
ELSE qs.statement_end_offset + 4 END -
qs.statement_start_offset)/2) AS QueryText,
Run Code Online (Sandbox Code Playgroud)
这将向您显示针对给定查询统计行运行的过程中的各个语句文本,而不是仅仅复制整个过程主体。