2

(一見すると、これは、ステートメントを直接実行する場合とストアド プロシージャから実行する場合の実行計画が異なるか、またはSqlServer オプティマイザーがパラメーターと混同されるのはなぜですか?の複製のように見えるかもしれませんが、実際の質問は少し異なります)

わかりました、これは私を数時間困惑させました。ここでの私の例は途方もなく抽象化されているため、ローカルで再作成できるとは思えませんが、質問のコンテキストを提供します (また、SQL Server 2005 を実行しています)。

基本的に 2 つのステップを持つストアド プロシージャがあります。一時テーブルを作成し、それに非常に少数の行を入力し、その一時テーブルに対して結合する非常に大きなテーブルをクエリします。複数のパラメーターがありますが、最も関連性の高いのはdatetime" @MinDate." です。基本的に:

create table #smallTable (ID int)

insert into #smallTable
select (a very small number of rows from some other table)

select * from aGiantTable
inner join #smallTable on #smallTable.ID = aGiantTable.ID
inner join anotherTable on anotherTable.GiantID = aGiantTable.ID
where aGiantTable.SomeDateField > @MinDate

これを通常のクエリとして実行するだけで@MinDate、ローカル変数として宣言して実行すると、非常に迅速に実行される最適な実行プランが生成されます (最初に #smallTable で結合し、実行中に aGiantTable からの行の非常に小さなサブセットのみを考慮します)。その他の操作)。#smallTable が小さいことに気付いたようですので、そこから始めると効率的です。これはいい。

ただし、それを@MinDateパラメーターとしてストアド プロシージャにすると、完全に非効率的な実行プランが生成されます。(私は毎回再コンパイルしているので、キャッシュされた計画は悪くありません...少なくとも、そうではないことを願っています)

しかし、ここからが奇妙になります。procを次のように変更すると:

declare @LocalMinDate datetime
set @LocalMinDate = @MinDate --where @MinDate is still a parameter

create table #smallTable (ID int)

insert into #smallTable
select (a very small number of rows from some other table)

select * from aGiantTable
inner join #smallTable on #smallTable.ID = aGiantTable.ID
inner join anotherTable on anotherTable.GiantID = aGiantTable.ID
where aGiantTable.SomeDateField > @LocalMinDate

次に、効率的な計画を教えてくれます。


したがって、私の理論は次のとおりです。(ストアド プロシージャとしてではなく) プレーン クエリとして実行する場合、最後の最後まで高価なクエリの実行プランの構築を待機するため、クエリ オプティマイザーは #smallTable が小さいことを認識し、その情報を使用します。効率的なプランを提供します。

ただし、ストアド プロシージャとして実行すると、実行プラン全体が一度に作成されるため、この情報を使用してプランを最適化することはできません。

しかし、ローカルで宣言された変数を使用すると、なぜこれが変わるのでしょうか? それが実行計画の作成を遅らせるのはなぜですか? それは実際に起こっていることですか?もしそうなら、この方法でローカル変数を使用していない場合でも、遅延コンパイルを強制する方法はありますか (それが実際にここで起こっている場合)。

より一般的には、ストアド プロシージャの各ステップの実行計画がいつ作成されるかについて、情報源を持っている人はいますか? グーグルは役立つ情報を提供していませんが、正しいものを探しているとは思いません. それとも、私の理論は完全に根拠のないものですか?

編集:投稿以来、私はパラメータスニッフィングについて学びました.これが実行計画のコンパイルを早める原因であると思います(ストアドプロシージャが実際に一度にすべてコンパイルされない限り). またはスニッフィングを完全に無効にしますか?

select * from aGiantTableを置き換えることでより効率的な計画を強制できるため、問題は学術的なものです。

select * from (select * from aGiantTable where ID in (select ID from #smallTable)) as aGiantTable

または、それを吸い上げてパラメーターをマスキングするだけですが、それでも、この矛盾は非常に興味深いものです。


tl;dnr

これはとてつもなく長い質問なので、簡単に言うと:

完全な実行計画は、ストアド プロシージャが最初に呼び出されたとき、または実行時に作成されますか? つまり、ストアド プロシージャが複数のステップで構成されている場合、各ステップの実行計画は、プロシージャが最初に呼び出されたときに作成されますか?それとも、過去のステップの実行が終了した後にのみ作成されますか?

4

2 に答える 2

2

これはパラメータースニッフィングであり、SQLServer2008とOPTIMIZEFORUNKNOWNがない場合は、(見つけたように)ローカル変数でパラメーターをマスキングするのが最善の策です。

于 2010-04-15T04:49:53.903 に答える
1

あなたが見るためのいくつかの追加記事:

http://blogs.msdn.com/queryoptteam/archive/2006/03/31/565991.aspx http://sqlblog.com/blogs/ben_nevarez/archive/2009/08/27/the-query-optimizer-and -parameter-sniffing.aspx

「再コンパイル」クエリ オプションを使用して、「パラメータ スニッフィング」を回避することもできることに注意してください。

于 2010-04-14T21:59:08.223 に答える