web-dev-qa-db-ja.com

クラスター化インデックスと非クラスター化インデックスのパフォーマンスの違い

ClusteredNon Clustered Indexesを読んでいました。

Clustered Index-データページが含まれます。つまり、完全な行情報がクラスター化インデックス列に表示されます。

Non Clustered Index-クラスターインデックス列(利用可能な場合)またはファイル識別子+ページ番号+ページ内の合計行の形式の行ロケーター情報のみが含まれます。つまり、クエリエンジンは、実際のデータを見つけるために追加の手順を実行する必要があります。

Query-テーブルにはClustered Indexを1つだけ含めることができ、Clustered Index Columnsortingを提供し、Non Clustered Indexsortingを提供せず、サポートできるので、実際の例を使用してパフォーマンスの違いを確認するにはどうすればよいですか? Non Clustered Indexesの999 SQL Server 2008およびSQL Server 2005の249。

22
Pankaj Garg

それはとても重要な概念なので非常に良い質問です。ただし、これは大きなトピックであり、基本的な概念を理解できるように、ここでは簡略化して説明します。

最初に、クラスター化インデックスが表示されたときに、テーブルを考えます。 SQL Serverでは、テーブルにクラスター化インデックスが含まれていない場合、それはヒープです。テーブルにクラスター化インデックスを作成すると、実際にはテーブルがBツリー型の構造に変換されます。クラスタ化インデックスISあなたのテーブルそれはテーブルから分離されていません

なぜクラスター化インデックスを1つしか持てないのか疑問に思ったことはありませんか? 2つのクラスター化インデックスがある場合、テーブルのコピーが2つ必要になります。結局のところ、データが含まれています。

これを試して、簡単な例を使って説明します。

注:この例ではテーブルを作成し、300万以上のランダムエントリを入力しました。次に、実際のクエリを実行し、実行プランをここに貼り付けました。

実際に把握する必要があるのは、O表記または運用効率。次の表があるとします。

CREATE TABLE [dbo].[Customer](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[CustomerName] [varchar](100) NOT NULL,
[CustomerSurname] [varchar](100) NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
[CustomerID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , IGNORE_DUP_KEY = OFF,ALLOW_ROW_LOCKS  = ON
  , ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

したがって、ここにはCustomerIDにクラスター化されたキーを持つ基本的なテーブルがあります(主キーはデフォルトでクラスター化されています)。したがって、テーブルは主キーCustomerIDに基づいて配置/順序付けされます。中間レベルには、CustomerID値が含まれます。データページには行全体が含まれるため、テーブル行になります。

また、CustomerNameフィールドに非クラスター化インデックスを作成します。次のコードはそれを行います。

CREATE NONCLUSTERED INDEX [ix_Customer_CustomerName] ON [dbo].[Customer] 
 (
[CustomerName] ASC
 )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF
  , DROP_EXISTING = OFF, ONLINE = OFF
  , ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

したがって、このインデックスでは、データページ/リーフレベルのノードで、クラスター化インデックスの中間レベルへのポインターが見つかります。インデックスは、CustomerNameフィールドの周囲に配置/順序付けされます。したがって、中間レベルにはCustomerName値が含まれ、リーフレベルにはポインターが含まれます(これらのポインター値は実際には主キー値またはCustomerID列です)。

次のクエリを実行すると、

SELECT * FROM Customer WHERE CustomerID = 1 

SQLは、シーク操作によってクラスター化インデックスを読み取る可能性があります。シーク操作は、逐次検索であるスキャンよりもはるかに効率的なバイナリ検索です。したがって、上記の例ではインデックスが読み取られ、バイナリ検索SQLを使用することで、探している基準に一致しないデータを除外できます。クエリプランについては、添付のスクリーンショットを参照してください。

enter image description here

したがって、操作の数またはシーク操作のO表記は次のとおりです。

  1. 検索された値を中間レベルの値と比較して、クラスター化インデックスのバイナリ検索を実行します。
  2. 一致する値を返します(クラスター化インデックスにはすべてのデータが含まれているため、行データなので、インデックスからすべての列を返すことができるので注意してください)

つまり、2つの操作です。ただし、次のクエリを実行した場合:

SELECT * FROM Customer WHERE CustomerName ='John'

SQLは、CustomerNameの非クラスター化インデックスを使用して検索を実行します。ただし、これは非クラスター化インデックスであるため、行のすべてのデータが含まれるわけではありません。

したがって、SQLは中間レベルで検索を行って一致するレコードを見つけ、返された値を使用してルックアップを実行し、クラスター化インデックス(別名テーブル)で別の検索を行って実際のデータを取得します。これは私が知っていると混乱するように聞こえますが、読んでください。すべてが明らかになります。

非クラスター化インデックスには、CustomerNameフィールド(中間ノードに格納されているインデックス付きフィールド値)とCustomerIDであるデータへのポインターしか含まれていないため、インデックスにはCustomerSurnameのレコードがありません。 CustomerSurnameは、クラスター化インデックスまたはテーブルからフェッチする必要があります。

このクエリを実行すると、次の実行プランが表示されます。

enter image description here

上記のスクリーンショットで気づく2つの重要な点があります。

  1. SQLは、インデックスが不足していると言っています(緑色のテキスト)。 SQLは、CustomerIDとCustomerSurnameを含むインデックスをCustomerNameに作成することを提案しています。
  2. また、クエリ時間の99%が主キーインデックス/クラスター化インデックスのキールックアップに費やされていることもわかります。

SQLがCustomerNameのインデックスを再度提案するのはなぜですか?インデックスにはCustomerIDのみが含まれ、CustomerName SQLはテーブル/クラスター化インデックスからCustomerSurnameを見つける必要があるためです。

インデックスを作成し、インデックスにCustomerSurname列を含めた場合、SQLは非クラスター化インデックスを読み取るだけでクエリ全体を満たすことができます。これが、SQLが非クラスター化インデックスの変更を提案している理由です。

ここでは、クラスター化されたキーからCustomerSurname列を取得するためにSQLが実行する必要がある追加の操作を確認できます。

したがって、操作の数は次のとおりです。

  1. 検索された値を中間レベルの値と比較して、非クラスター化インデックスのバイナリ検索を実行します
  2. 一致するノードについては、クラスター化インデックス内のデータへのポインターを含むリーフレベルノードを読み取ります(ちなみに、リーフレベルノードには主キー値が含まれます)。
  3. 返された値ごとにクラスター化インデックス(テーブル)を読み取り、ここで行の値を取得して、CustomerSurnameを読み取ります。
  4. 一致する行を返す

これは、値を取得するための4つの操作です。クラスター化インデックスの読み取りと比較して、必要な操作の量が2倍になります。すべてのデータが含まれているため、クラスター化インデックスが最も強力なインデックスであることを示しています。

最後にもう1つポイントを明確にしておきます。非クラスター化インデックスのポインターが主キー値であると言うのはなぜですか?非クラスター化インデックスのリーフレベルノードに主キー値が含まれていることを示すために、クエリを次のように変更します。

SELECT CustomerID
FROM Customer
WHERE CustomerName='Jane'

このクエリでは、SQLは非クラスター化インデックスからCustomerIDを読み取ることができます。クラスタ化インデックスを検索する必要はありません。これはこのような実行計画で見ることができます。

enter image description here

このクエリと前のクエリの違いに注意してください。ルックアップはありません。 SQLは非クラスター化インデックス内のすべてのデータを見つけることができます

うまくいけば、クラスタ化インデックスがテーブルであり、非クラスタ化インデックスがすべてのデータを含んでいないことを理解し始めることができます。バイナリ検索は実行できますが、すべてのデータが含まれるのはクラスター化インデックスのみであるため、インデックス作成により選択が高速化されます。したがって、非クラスター化インデックスを検索すると、ほとんどの場合、クラスター化インデックスから値が読み込まれます。これらの追加操作により、非クラスター化インデックスはクラスター化インデックスよりも効率が低下します。

これで問題が解決することを願っています。意味がわからない場合はコメントを投稿してください。明確にしていきます。ここはかなり遅く、私の脳は少し平坦になっています。レッドブルの時間です。

43
Namphibian

「これは、クエリエンジンが実際のデータを見つけるために追加の手順を実行する必要があることを意味します。」

必ずしもそうとは限りません-インデックスが特定のクエリをカバーしている場合、データページに移動する必要はありません。また、含まれる列を使用して、非クラスター化インデックスに列を追加して、キーサイズを変更せずにそれをカバーすることができます。

したがって、最終的な答えは-それは依存する(実際に1つの質問でカバーできるよりも多くの情報に依存する)-インデックスのすべての機能を理解する必要があり、特定のクエリの実行プランは期待とは異なる場合があります。

私の一般的な経験則では、テーブルには常にクラスター化インデックス(および通常はIDまたは順次GUID)がありますが、非クラスター化インデックスはパフォーマンスのために追加されます。ただし、例外は常にあります。ヒープテーブルには場所があり、より広いクラスター化インデックスには場所があります。ページごとにより多くの行を収めるために狭く見える一見冗長なインデックスは、場所があります。などなど.

そして、許可されているさまざまなインデックスの制限について心配する必要はありません。これは、多くの実際の例ではほとんど機能しません。

9
Cade Roux