次の例を考えてみましょう:
IF OBJECT_ID('dbo.my_table') IS NOT NULL
DROP TABLE [dbo].[my_table];
GO
CREATE TABLE [dbo].[my_table]
(
[id] int IDENTITY (1,1) NOT NULL PRIMARY KEY,
[foo] int NULL,
[bar] int NULL,
[nki] int NOT NULL
);
GO
/* Insert some random data */
INSERT INTO [dbo].[my_table] (foo, bar, nki)
SELECT TOP (100000)
ABS(CHECKSUM(NewId())) % 14,
ABS(CHECKSUM(NewId())) % 20,
n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
ON [dbo].[my_table] ([nki] ASC);
GO
[nki]
(非クラスター化インデックス)で並べられたすべてのレコードをフェッチすると、次のようになります。
SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;
SQL Server Execution Times: CPU time = 266 ms, elapsed time = 493 ms
オプティマイザーはクラスター化インデックスを選択し、並べ替えアルゴリズムを適用します。
しかし、非クラスター化インデックスを使用するように強制すると、次のようになります。
SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table WITH(INDEX(IX_my_TABLE));
SET STATISTICS TIME OFF;
SQL Server Execution Times: CPU time = 311 ms, elapsed time = 188 ms
次に、キールックアップで非クラスター化インデックスを使用します。
明らかに、非クラスター化インデックスがカバーするインデックスに変換される場合:
CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
ON [dbo].[my_table] ([nki] ASC)
INCLUDE (id, foo, bar);
GO
次に、このインデックスのみを使用します。
SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;
SQL Server Execution Times: CPU time = 32 ms, elapsed time = 106 ms
後者の場合、実行時間が38%速くても、SQL Serverは非クラスター化インデックスを使用する代わりにクラスター化インデックスと並べ替えアルゴリズムを使用するのはなぜですか?
SQL Serverは、ランタイム情報ではなく、統計に基づいたコストベースのオプティマイザーを使用するためです。
このクエリのコスト見積もりプロセスでは、実際にルックアッププランを評価しますが、より多くの労力がかかると見積もります。 (実行プランでSELECTにカーソルを合わせると、「推定サブツリーコスト」に注意してください)。これも必ずしも悪い仮定ではありません。私のテストマシンでは、ルックアッププランは並べ替え/スキャンのCPUの6倍を占めています。
SQL Serverが検索プランのコストを高くする理由について、Rob Farleyの回答をご覧ください。
100,000回のルックアップに必要な読み取りの数と並べ替えの実行に必要な読み取りの数を比較すると、クエリオプティマイザーがCIX + Sortが最良の選択であると計算する理由がすぐにわかります。
読み込まれているページがメモリ内にあるため、ルックアップの実行が速くなります(キャッシュをクリアした場合でも、ページごとに多くの行があり、同じページを何度も読み込みますが、断片化の量が異なります)または他のアクティビティとは異なるメモリプレッシャー、これはそうではないかもしれません)。 CIX + Sortを高速化するのにそれほど多くの時間はかかりませんが、読み取りのコストは、同じページを繰り返しヒットすることの相対的な安さを考慮していないためです。
私はこの質問について少し掘り下げることにしました。非クラスター化インデックスの使用(強制)ではなく、どのように、いつ使用するか、またはおそらくより良い方法について話している興味深いドキュメントを見つけました。
John Eisbrener によるコメントで示唆されているように、他のブログでも最も参照されているものの1つが、キンバリーL.トリップの興味深い記事です。
ただし、これだけではありません。興味がある場合は、このページをご覧ください。
ご覧のように、それらはすべてTipping pointの概念の周りを移動します。
K.L.から引用トリップ記事
転換点とは
これは、返される行数が「選択性が不十分」になるポイントです。 SQL Serverは、対応するデータ行を検索するために非クラスター化インデックスを使用しないことを選択し、代わりにテーブルスキャンを実行します。
SQL Serverがヒープで非クラスター化インデックスを使用する場合、基本的には、ベーステーブルのページへのポインターのリストを取得します。次に、これらのポインターを使用して、行IDルックアップ(RID)と呼ばれる一連の操作で行を取得します。これは、少なくとも、返された行数と同じ数のページ読み取りを使用することを意味します。プロセスはクラスタ化インデックスをベーステーブルとして使用する場合と多少似ていますが、同じ結果が得られます。
しかし、その転換点がいつ発生しますか?
もちろん、この人生のほとんどのものと同じように、それは次のように異なります...
深刻ではありません。ページあたりの行数に応じて、テーブル内のページ数の25%から33%の間で発生します。ただし、考慮すべき要素が他にもあります。
ITPRoTodayの記事からの引用
転換点に影響を与えるその他の要因RIDルックアップのコストは転換点に影響を与える最も重要な要因ですが、他にもいくつかの要因があります。
- クラスタ化インデックスをスキャンする場合、物理I/Oははるかに効率的です。クラスター化インデックスデータは、インデックス順にディスクに順番に配置されます。その結果、ディスク上で横方向のヘッド移動がほとんどなくなり、I/Oパフォーマンスが向上します。
- データベースエンジンがクラスター化インデックスをスキャンしているとき、ディスクトラックの次の数ページに必要なデータが含まれている可能性が高いことがわかります。そのため、通常の8KBページではなく、64KBチャンクで先読みを開始します。これにより、I/Oも高速になります。
ここで、統計IOを使用してクエリを再度実行すると、次のようになります。
SET STATISTICS IO ON;
SELECT id, foo, bar, nki FROM my_table WHERE nki < 20000 ORDER BY nki ;
SET STATISTICS IO OFF;
Logical reads: 312
SET STATISTICS IO ON;
SELECT id, foo, bar, nki FROM my_table WITH(INDEX(IX_my_TABLE));
SET STATISTICS IO OFF;
Logical reads: 41293
2番目のクエリには、最初のクエリよりも多くの論理読み取りが必要です。
非クラスタ化インデックスを避けるべきですか?
いいえ、クラスター化インデックスは役に立ちますが、時間をかけて、それを使って何を達成しようとしているのかを分析するために特別な努力をする価値があります。
K.L.から引用トリップ記事
それで、あなたは何をすべきですか?場合によります。データをよく理解していて、いくつかの広範なテストを行う場合は、ヒントを使用することを検討してください(spsでプログラムで実行できる巧妙なことがいくつかあるので、すぐに投稿します)。しかし、(もし可能であれば)はるかに良い選択は、カバーすることを検討することです(それが本当に私の主なポイントです:)。私のクエリでは、すべての列を必要とするため(悪意のあるSELECT *)、カバリングは非現実的ですが、クエリが狭く、優先度が高い場合、ヒントよりもカバリングインデックス(多くの場合)の方が適しています。クエリをカバーするインデックスで、ヒントはありません。
それが今のところパズルへの答えですが、さらに深く学ぶべきことがたくさんあります。転換点は非常に良いことであり、通常はうまく機能します。ただし、インデックスを強制してパフォーマンスを向上できることがわかった場合は、調査を行い、それがこれであるかどうかを確認することをお勧めします。次に、ヒントがどれほど役立つ可能性があるかを考えます。これで、どこに集中できるかがわかります。