web-dev-qa-db-ja.com

sp_Blitz、CREATE PROCEDUREの> 1000プラン

私は Brent Ozar'ssp_Blitz を使用していますが、その結果の1つは次のとおりです。

1つのクエリに対する多くのプラン

1146プランは、プランキャッシュ内の1つのクエリに対して存在します。つまり、おそらくパラメーター化の問題があります。

結果のリンク には次のクエリがあります。

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;

...プランの数が最も多いクエリが表示されます。

これを実行すると、上位の結果の1つが得られます(同じクエリハッシュに対して約1150の計画があります)は私を困惑させます:

query result

多分それはスクリーンショットで認識するのが少し難しいです-これは完全ですCREATE PROCEDURE定義、コメントを含む、次のように:

-- =============================================
-- 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

さらに、それらはすべて同じです。
QueryTextを複数の行からテキストファイルにコピーして差分を作成しましたが、すべて100%同一です。

なぜこれが起こるのか?

私たちのデータベースオブジェクトはソース管理下にあるため、この特定のSPは約2か月前に最後に変更されました。1日に複数回削除して再作成する場合でも(これは行いません)、SQL Serverが同じクエリに対して多くのプランを作成する理由がまだわかりません。


このSPについて特別なことはありません。ただし、これは Table-Valued Parameters を使用する非常に少数のSPの1つです。

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

Aaron Bertrandの変更されたクエリ を実行すると、最後に2つの単純なUPDATEステートメントが返されます。

つまり同じクエリハッシュで最大1150行を取得していますが、それらの半分には次のクエリテキストがあります。

update #tmp set ordertype = 'A' where producttype = 4

...そして他の人はこれを持っています:

update #tmp set ordertype = 'B' where producttype = 2
2

質問1:「CREATE PROCEDUREとは?!?」ストアドプロシージャを実行すると、SQL Serverはストアドプロシージャのテキスト全体をThingとして保存しますきみが呼んだ。

ストアドプロシージャを作成するのではなく、実行するだけでしたが、プランキャッシュの分析を始めたばかりの人にとっては、これは少し混乱する可能性があります。

だからねえ、あなたは今そのハードルを超えています!やった、あなた!

質問2:「1つのストアドプロシージャに複数のプランを設定するにはどうすればよいですか?」その全文を見ないと、わかりにくいですが、始めますErland Sommarskogの叙事詩の投稿 アプリで遅い、SSMSで速い 。特に、「 異なる設定の異なるプラン 」というタイトルのセクションを確認してください。

私は実際にはそれが問題だとは思っていません-procテキスト内に何か動的なものがあるに違いありません-しかし、ここに正確なコードを投稿したくないことは理解しています。正確なコードを確認しないと、部外者がその特定の質問に答えることは困難です。

更新:ミステリーが解決されました。このストアドプロシージャはたまたまテーブル値のパラメーターを使用すると偶然に言及しました。 これはTVPの呼び出し方法に関する既知の問題です これは、質問があるcompleteコードを含めることが非常に重要である理由の良い例です約-時には、ごく小さなことでも、質問に大きな影響を与えることがあります。

7
Brent Ozar

これは全体的な質問に対する回答ではありませんが、それらの行で表される個々のステートメントについてより詳細に取得する方法を示しています。現在、クエリは、そのクエリ統計行に対して収集されたプロシージャの部分を絞り込むのではなく、プロシージャのテキスト全体を取得するだけです。これはコメントとして恐ろしいことだったでしょう。

変化する:

st.text AS QueryText,

に:

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,

これにより、プロシージャ本体全体を単にコピーするのではなく、特定のクエリ統計行に対して実行されたプロシージャ内の個々のステートメントテキストが表示されます。

3
Aaron Bertrand