web-dev-qa-db-ja.com

SQL Serverクエリ:リテラルでは高速だが変数では低速

CTEを使用してテーブルから2つの整数を返すビューがあります。このようなビューをクエリすると、1秒未満で実行されます

SELECT * FROM view1 WHERE ID = 1

ただし、このようにビューをクエリすると、4秒かかります。

DECLARE @id INT = 1
SELECT * FROM View1 WHERE ID = @id

2つのクエリプランを確認しました。最初のクエリはメインテーブルでクラスター化インデックスシークを実行して1レコードを返し、残りのビュークエリをその結果セットに適用しています。2番目のクエリはインデックススキャンを実行しているため、関心のあるレコードだけでなく、約3000レコードのレコードを返し、後で結果セットをフィルタリングします。

インデックススキャンではなくインデックスシークを使用する2番目のクエリを取得するために欠けている明らかなことはありますか? SQL 2008を使用していますが、SQL 2005でも実行する必要があるものは何でもあります。最初はある種のパラメータースニッフィングの問題だと思いましたが、キャッシュをクリアしても同じ結果が得られます。

42
Gavin

パラメータの場合、オプティマイザは値がnullではないことを認識できないため、それが正しい場合でも正しい結果を返す計画を作成する必要があるためと考えられます。 SQL Server 2008 SP1を使用している場合は、クエリにOPTION(RECOMPILE)を追加してみてください。

46
erikkallen

OPTIMIZE FOR hint をクエリに追加できます。

DECLARE @id INT = 1
SELECT * FROM View1 WHERE ID = @id OPTION (OPTIMIZE FOR (@ID = 1))

私の場合、DBテーブルの列の型がVarCharとして定義され、パラメーター化されたクエリパラメーターの型がNVarCharとして定義されていたため、実際の実行プランにCONVERT_IMPLICITが導入され、比較前にデータ型を照合しました。これが種のパフォーマンスの原因でした2秒vs 11秒パラメータタイプを修正するだけで、パラメータ化クエリが非パラメータ化バージョンと同じくらい高速になりました。

これが同様の問題を持つ誰かを助けることを願っています。

3
Morbia

SQLが変数を使用したクエリのクエリプランの最適化を開始すると、使用可能なインデックスを列と照合します。この場合、インデックスがあったため、SQLはインデックスをスキャンして値を探すだけだと考えました。 SQLが列とリテラル値を使用してクエリの計画を立てたとき、統計と値を調べて、インデックスをスキャンする必要があるかどうか、またはシークが正しいかどうかを判断できます。

最適化のヒントと値を使用すると、SQLに「これはほとんどの場合使用される値なので、この値に対して最適化する」ことを伝え、プランはこのリテラル値が使用されたかのように格納されます。 UNKNOWNの最適化ヒントとサブヒントを使用すると、SQLは値がどうなるかわからないため、SQLは列の統計を見て、シークまたはスキャンが最適であるかを判断し、それに応じて計画を立てます。

1
RC_Cleland

私は直接割り当て(WHERE UtilAcctId = 12345)で10ミリ秒未満で実行されたビューでこの問題に遭遇しましたが、変数割り当て(WHERE UtilAcctId = @UtilAcctId)。
後者の実行プランは、テーブル全体でビューを実行した場合と同じでした。

私のソリューションでは、大量のインデックス、オプティマイザヒント、または長い統計の更新は必要ありませんでした。

代わりに、ビューをUser-Table-Functionに変換しました。ここで、パラメーターはWHERE句で必要な値でした。実際、このWHERE句は3つのクエリの深さにネストされていましたが、それでも機能し、速度は10ミリ秒未満に戻りました。

最終的に、パラメーターをUtilAcctIds(int)のテーブルであるTYPEに変更しました。次に、WHERE句をテーブルのリストに制限できます。 WHERE UtilAcctId = [parameter-List] .UtilAcctId。これはさらにうまくいきます。ユーザーテーブル関数はプリコンパイルされていると思います。

1
Michael Barash

私もこの同じ問題に出くわしましたが、サブクエリの結果に対する(左)結合を含むインデックスが見つからないことがわかりました。

select *
from foo A
left outer join (
  select x, count(*)
  from bar
  group by x
) B on A.x = B.x

Bar.xにbar_xという名前のインデックスを追加

0
Sandman
DECLARE @id INT = 1

SELECT * FROM View1 WHERE ID = @id

これを行う

DECLARE @sql varchar(max)

SET @sql = 'SELECT * FROM View1 WHERE ID =' + CAST(@id as varchar)

EXEC (@sql)

あなたの問題を解決します

0
Neeraj kalyan