tempdbへの流出の可能性を減らすために、行推定をどのように改善できるか


11

tempdbイベントへの流出(遅いクエリの原因)が発生すると、特定の結合で行の見積もりがずれることがよくあります。マージイベントとハッシュ結合で流出イベントが発生し、ランタイムが3倍から10倍に増えることがよくあります。この質問は、流出事故の可能性を減らすことを前提として、行の見積もりを改善する方法に関係しています。

行の実際の数40k。

このクエリの場合、プランは不適切な行の見積もり(11.3行)を示しています。

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

このクエリの場合、プランは適切な行推定(56k行)を示しています。

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

最初のケースの行推定を改善するために、統計またはヒントを追加できますか?特定のフィルター値(プロパティ= 2840)で統計を追加しようとしましたが、組み合わせを正しく取得できなかったか、コンパイル時にObjectIdが不明であり、すべてのObjectIdの平均を選択している可能性があるため、無視されている可能性があります。

最初にプローブクエリを実行し、それを使用して行の推定値を決定するモードや、盲目的に飛行するモードはありますか?

この特定のプロパティには、少数のオブジェクトでは多くの値(40k)があり、大多数ではゼロです。特定の結合の予想される最大行数を指定できるというヒントに満足しています。一部のパラメータは結合の一部として動的に決定されるか、ビュー内に配置する方がよい(変数のサポートなし)ため、これは一般的に悩ましい問題です。

tempdbへの流出の可能性を最小限に抑えるために調整できるパラメーターはありますか(たとえば、クエリごとの最小メモリ)。堅牢な計画は、見積もりに影響を与えませんでした。

2013.11.06を編集:コメントおよび追加情報への応答:

クエリプランの画像は次のとおりです。警告は、convert()のカーディナリティ/シーク述語に関するものです。

ここに画像の説明を入力してください ここに画像の説明を入力してください

@Aaron Bertrandのコメントに従って、テストとしてconvert()を置き換えてみました。

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

ここに画像の説明を入力してください

奇妙だが成功した興味深い点として、ルックアップを短絡させることもできました:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

ここに画像の説明を入力してください

これらは両方とも適切なキールックアップをリストしますが、最初のものだけがObjectIdの「出力」をリストします。2番目が確かに短絡であることを示していると思いますか?

誰かが行推定を支援するために単一行プローブが実行されたかどうかを確認できますか?単一行のPKルックアップでヒストグラムへのルックアップの精度を大幅に向上できる場合(特に、流出の可能性や履歴がある場合)、最適化をヒストグラム推定のみに制限するのは間違っているようです。実際のクエリにこれらのサブジョインが10個ある場合、理想的にはそれらは並行して発生します。

余談ですが、sql_variantはその基本型(SQL_VARIANT_PROPERTY = BaseType)をフィールド自体に格納するため、「直接」変換可能(たとえば、文字列を10進数に変換するのではなく、intからint intまたは多分intからbigint)。コンパイル時にはわかりませんが、ユーザーにはわかる場合があるため、sql_variantsの「AssumeType(type、...)」関数を使用すると、透過的に処理できるようになります。


1
最初の推測は、bigintへの変換が推定をスローすることです(クエリプランにはSQL Server 2012でそれが警告されます)一方で、サブクエリは0または1行以外を返すことができませんでしたクエリが成功した場合。あなたが持っているクエリプランを見ることは興味深いでしょう。おそらく、XMLバージョンへのリンクとして。
ミカエルエリクソン2013年

2
サブクエリをインライン化すると何が得られますか?個別に引き出す方が全体的にはっきりしていることをお勧めします。見積もりを改善するため、その方法を使用しないのはなぜですか。
アーロンバートランド

2
SQL Serverのバージョンは何ですか?問題の詳細を確認できるように、テーブルとインデックスのDDLとテーブルの統計BLOB(単一列と複数列)を提供できますか?declare @a bigint = あなたが行ったように使用してクエリを分割することは私にとって自然な解決策のようですが、なぜそれは受け入れられないのですか?
ポールホワイト9

2
あなたの設計は(非常に単純化した)EAV設計でありCONVERT()、列で使用してから結合することを強制していると思います。これは確かにほとんどの場合効率的ではありません。この特定のものでは、変換される値は1つだけなので、それはおそらく問題ではありませんが、テーブルにはどのインデックスがありますか?EAV設計は通常、適切なインデックス付け(つまり、通常は狭いテーブルに多数のインデックスが作成される)を使用した場合にのみ、適切に機能します。
ypercubeᵀᴹ

@Paul White、分割する限り...これはこのケースの解決策としては問題ありません。しかし、より一般的で複雑なものについては、並列化と読みやすさをあきらめたくありません。たとえば、クエリ内に10個のサブクエリ(一部はより複雑なもの)がありますが、残りのクエリを開始する前に「熟成」する必要があるのは5つだけです。これらの5つがどれであるかを把握する必要がないようにしたいとします。
crokusek 2013年

回答:


7

クエリは非常に単純であり、考慮が必要なため、流出、tempdb、ヒントについてはコメントしません。クエリに適したインデックスがあれば、SQL-Serverのオプティマイザは非常にうまく機能すると思います。

また、2つのクエリに分割すると、どのインデックスが役立つかがわかるので便利です。最初の部分:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

上のインデックス必要(PropertyId, ObjectId, Sequence)含めをValueUNIQUE安全にしようと思います。複数の行が返された場合、クエリは実行時にエラーをスローします。そのため、一意のインデックスを使用して、これが発生しないことを事前に確認することをお勧めします。

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

クエリの2番目の部分:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

(PropertyId, ObjectId)含める上でのインデックスが必要Valueです:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

効率が改善されない場合、またはこれらのインデックスが使用されなかった場合、または表示される行推定に依然として差異がある場合は、このクエリをさらに調査する必要があります。

その場合、変換(EAV設計と同じ列に異なるデータ型を格納するために必要)は考えられる原因であり、(@ AAron Bertrandと@Paul Whiteのコメントのように)クエリを2つの部分に分割するソリューションは自然なようですそして行く方法。それぞれの列に異なるデータ型を持つように再設計することは別の場合があります。


表はインデックスをカバーしていた-私は質問でそれを述べるべきだった。この例は実際にはサブ結合であり、より大きなクエリをフィードするため、tempdbの流出に関するすべての大騒ぎがあります。
crokusek 2013年

5

統計の改善に関する明確な質問に対する部分的な回答として...

行が個別に分割された場合でも、行の推定値はまだ10倍ずれていることに注意してください(4k対予想される40k)。

統計ヒストグラムは、長い(垂直)3.5M行テーブルであり、その特定のプロパティは非常にまばらであるため、そのプロパティには薄すぎる可能性があります。

スパースプロパティの追加の統計(IX統計では多少冗長)を作成します。

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

オリジナル:

ここに画像の説明を入力してください ここに画像の説明を入力してください

convert()を削除すると(適切):

ここに画像の説明を入力してください

convert()を削除すると(ショートサーキット):

ここに画像の説明を入力してください

オブジェクトの99.9%以上にプロパティ2840がまったく定義されていないため、まだ2X程度ずれている可能性があります。実際、このテストケースでは、プロパティは3.5M行テーブルの200kの個別オブジェクトの1つにのみ存在します。その驚くべきことは、本当にそれに近づきました。より少ないObjectIdになるようにフィルターを調整し、

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

うーん、変更なし...統計の最後に「フルスキャンあり」が追加されたことを裏付け(前の2つが機能しなかった理由かもしれません)、そうです:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

ここに画像の説明を入力してください

わーい。そのため、IXを広くカバーする非常に垂直なテーブルでは、フィルターされた統計を追加することで大幅な改善が見られます(特に、スパースで非常に多様なキーの組み合わせの場合)。


複数列統計に関するいくつかの問題のある問題へのリンク。
crokusek 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.