クエリパフォーマンスの向上について この質問 のフォローアップとして、インデックスをデフォルトで使用する方法があるかどうか知りたいのですが。
このクエリは約2.5秒で実行されます。
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
これは約33msで実行されます:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
ORDER BY [DateEntered], [DeviceID];
[ID]フィールド(pk)にクラスター化インデックスがあり、[DateEntered]、[DeviceID]に非クラスター化インデックスがあります。最初のクエリはクラスター化インデックスを使用し、2番目のクエリは非クラスター化インデックスを使用します。私の質問は2つの部分です:
最初のクエリは、前に説明したしきい値に基づいてテーブルスキャンを実行します。 数百万行の狭いテーブルでクエリのパフォーマンスを向上させることは可能ですか?
(おそらくTOP 1000
句のないクエリは、46,000行以上、または35kと46kの間の行を返します。(灰色の領域;-))
2番目のクエリは順序付けする必要があります。 NCインデックスは必要な順序で並べられているので、オプティマイザがそのインデックスを使用してから、クラスター化インデックスをブックマークで検索して、欠落している列をクラスター化インデックススキャンの実行と比較し、必要に応じて取得する方が安価です。それを注文する。
ORDER BY
句の列の順序を逆にすると、NC INDEXが役に立たなくなるため、クラスター化インデックススキャンに戻ります。
2番目の質問への回答を忘れてしまいました。なぜこれを望まないのですか
非クラスター化非カバーインデックスを使用すると、行IDがNCインデックスで検索され、欠落している列をクラスター化インデックスで検索する必要があります(クラスター化インデックスにはテーブルのすべての列が含まれます)。クラスタ化インデックスで欠落している列を検索するIOは、ランダムIOです。
ここでの鍵はランダムです。 NCインデックスで見つかったすべての行について、アクセスメソッドはクラスター化インデックスで新しいページを検索する必要があるためです。これはランダムなので、非常に高価です。
さて、一方で、オプティマイザはクラスタ化インデックススキャンも実行できます。割り当てマップを使用してスキャン範囲を検索し、大きなチャンクでクラスター化インデックスの読み取りを開始できます。これは順次であり、はるかに安価です。 (テーブルがフラグメント化されていない限り:-))欠点は、完全なクラスター化インデックスを読み取る必要があることです。これはあなたのバッファにとっては悪いことであり、潜在的に大量のIOです。それでもシーケンシャルIO。
あなたの場合、オプティマイザは35k行と46k行の間のどこかを決定します。これは、完全なクラスタ化インデックススキャンよりも安価です。ええ、それは間違っています。そして、そのために選択的なWHERE
句または大きなテーブルを使用しない狭い非クラスター化インデックスの多くの場合、これは問題になります。 (テーブルも非常に狭いため、テーブルはさらに悪くなります。)
現在、ORDER BY
を追加すると、クラスタ化されたインデックス全体をスキャンしてから結果を並べ替えるのがより高価になります。代わりに、オプティマイザは、すべての順序付けられたNCインデックスを使用して、ブックマークの検索に対してランダムにIOペナルティを支払うことをお勧めします。
したがって、注文は完全な「クエリヒント」の種類のソリューションです。しかし、特定の時点で、クエリ結果が非常に大きくなると、ブックマークルックアップのランダムIOのペナルティが大きくなり、遅くなります。オプティマイザは計画をその時点より前のクラスタ化インデックススキャンに戻すと思いますが、確かなことはわかりません。
あなたの場合、チャットと前の質問(リンクを参照)で説明されているように、挿入がEnteredDateの順になっている限り、EnteredDate列にクラスター化インデックスを作成する方が適切です。
異なる構文を使用してクエリを表現すると、非クラスター化インデックスを使用したいという希望をオプティマイザーに伝えるのに役立つ場合があります。あなたはあなたが望む計画をあなたに与える以下のフォームを見つけるべきです:
SELECT
[ID],
[DeviceID],
[IsPUp],
[IsWebUp],
[IsPingUp],
[DateEntered]
FROM [dbo].[Heartbeats]
WHERE
[ID] IN
(
-- Keys
SELECT TOP (1000)
[ID]
FROM [dbo].[Heartbeats]
WHERE
[DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
AND [DateEntered] < CONVERT(datetime, '2011-08-31', 121)
);
そのプランを、非クラスター化インデックスがヒントで強制されたときに生成されたプランと比較します。
SELECT TOP (1000)
*
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE
[DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
計画は基本的に同じです(キールックアップはクラスター化インデックスのシークにすぎません)。両方のプランフォームは、非クラスター化インデックスに対して1回のシークとクラスター化インデックスへの最大1000のルックアップのみを実行します。
重要な違いは、トップ演算子の位置です。 2つのシークの間に位置するトップは、オプティマイザーが2つのシーク操作をクラスター化インデックスの論理的に同等のスキャンに置き換えるのを防ぎます。オプティマイザは、論理プランの一部を同等の関係演算で置き換えることにより機能します。 Topは関係演算子ではないため、書き換えによってクラスター化インデックススキャンに変換できなくなります。オプティマイザがTop演算子の位置を変更できたとしても、コストの見積もりが機能するため、シーク+ルックアップよりもスキャンが優先されます。
スキャンとシークのコスティング
非常に高いレベルでは、スキャンとシークのオプティマイザのコストモデルは非常に単純です。スキャンで1350ページを読み取ると、320のランダムシーク 同じコスト と推定されます。これはおそらく、特定の最新のI/Oシステムのハードウェア機能とほとんど似ていませんが、実用的なモデルとしては十分に機能します。
このモデルは、いくつかの単純化する仮定も行います。主なものは、すべてのクエリが、キャッシュにデータまたはインデックスページが存在しない状態で始まると想定されていることです。これは、すべてのI/Oが物理I/Oになることを意味しますが、実際にはほとんどありません。コールドキャッシュを使用した場合でも、プリフェッチと先読みは、クエリプロセッサがページを必要とするまでに、必要なページが実際にメモリに存在する可能性が非常に高いことを意味します。
別の考慮事項は、メモリ内にない行に対する最初の要求により、ページ全体がディスクからフェッチされることです。同じページの行に対する後続の要求では、物理I/Oは発生しません。原価計算モデルには、このような影響を考慮するロジックが含まれていますが、完全ではありません。
これらすべて(およびそれ以上)は、オプティマイザがおそらくそうするよりも早くスキャンに切り替える傾向があることを意味します。ランダムなI/Oは、物理的な操作が発生した場合にのみ、「シーケンシャル」I/Oよりも「はるかに高価」です。メモリ内のページへのアクセスは非常に高速です。物理的な読み取りが必要な場合でも、断片化のためにスキャンで順次読み取りがまったく発生しない場合があり、シークはパターンが基本的に順次になるように配置される場合があります。それに加えて、最新のI/Oシステム(特にソリッドステート)のパフォーマンス特性の変化により、全体が非常に不安定に見え始めます。
行の目標
プランにトップオペレーターが存在すると、原価計算アプローチが変更されます。オプティマイザは、スキャンを使用して1000行を検索する場合、クラスタ化されたインデックス全体をスキャンする必要がない可能性が高いことを認識できるほど賢く、1000行が見つかるとすぐに停止する可能性があります。 Top演算子で1000行の「行の目標」を設定し、統計情報を使用してそこから戻って、行ソース(この場合はスキャン)から必要になると予想される行数を推定します。この計算の詳細について ここ について書きました。
この回答の画像は SQL Sentry Plan Explorer を使用して作成されました。