web-dev-qa-db-ja.com

非クラスター化インデックスを主キーとして使用するパフォーマンス

背景

レコード管理システムのデータベースを設計しています。 INTまたはUNIQUEIDENTIFIERを主キーとして使用することを検討しているので、これは初期の段階です。

UNIQUEIDENTIFIERを検討する理由は、レコードを他のシステム(外部データベース)に移動できる機能が必要になる可能性が高く、GUIDとのすべての関係を処理することで、非常に簡単になるためです。 。

いくつかの調査を行った後、それぞれの長所と短所について多くの議論があり、私が関心を持っている主なものはパフォーマンスです。私の調査の結果、PKとしてUNIQUEIDENTIFIERを使用しても、テーブルのクラスター化インデックスとして使用されていない限り、問題にはなりません。

わかりましたので、明白な解決策はそれらを2つの別々の列に分割することです:

TableA

ID: INT, Identity, Clustered Index 
PK: UNIQUEIDENTIFIER, Non-clustered Index

質問

ここでの懸念は、関係を定義するときに、これが結合のパフォーマンスにどのように影響するかです。たとえば、次の「子」テーブルを見てください。

TableB

ID: INT, Identity, Clustered Index 
PK: UNIQUEIDENTIFIER, Non-clustered Index
FK: UNIQUEIDENTIFIER, Non-clustered Index

このアプローチを使用することによるパフォーマンスの低下、特にテーブルの結合について心配する必要がありますか?

たとえば、次のようなクエリを使用します。

SELECT * FROM TableB JOIN TableA ON TableA.PK = TableB.FK

基本的に、これはGUIDベースのPKをサポートするための最良の設計ですか、それともパフォーマンスの低下につながりますか?私が提案したアプローチを使用することには重大な欠点がありますか?

3
musefan

これは、GUIDをクラスター化された、およびクラスター化されていない主キーとして使用する数年に基づいて私が見たものです。正しい答えはここにはありません。重要なのは、データにアクセスするために使用するアクセスメソッドと、返されるデータの量です。

はい、ただし、アクセス方法でGUIDをクエリの述語として使用している場合、管理する必要があるGUIDクラスタ化インデックスで問題が発生します。 、そのテーブルから複数の列を返す場合、パフォーマンスを向上させてデータを読み戻すことによる影響を軽減するために、断片化の影響を受けた方がよい場合があります。

以下は、クラスター化インデックスの場合にデータを取り出すために必要な作業が少なく、データを取得するために使用される述語が非常に基本的な例です。

/* NonClustered PK */
CREATE TABLE #T1
    (
      C1 INT NOT NULL ,
      C2 UNIQUEIDENTIFIER NOT NULL ,
      C3 VARCHAR(100) NULL ,
      C4 VARCHAR(20) NULL ,
      CONSTRAINT PK_T1 PRIMARY KEY NONCLUSTERED ( C2 )
    );
CREATE CLUSTERED INDEX T1_C1 ON #T1 (C1);

/* Clustered PK */
CREATE TABLE #T2
    (
      C1 INT NOT NULL ,
      C2 UNIQUEIDENTIFIER NOT NULL ,
      C3 VARCHAR(100) NULL ,
      C4 VARCHAR(20) NULL ,
      CONSTRAINT PK_T2 PRIMARY KEY CLUSTERED ( C2 )
    );



/* Insert 10 rows into each table */
INSERT INTO #T1
        ( C1, C2, C3, C4 )
VALUES  ( 0, '58BBB460-1AFA-4177-BA78-798DA19E0C97', 'some text', 'C4 text')
, ( 1, '17E8163B-BE21-44C7-A7B7-4997265A139D', 'some text', 'C4 text')
, ( 2, '16AACAB8-CD77-4A8D-BE87-9E433CD157EC', 'some text', 'C4 text')
, ( 3, '787D0714-F92A-4963-89E5-3F5DBF518EA7', 'some text', 'C4 text')
, ( 4, '5C720476-D4BE-4047-9F73-DBB1B6B75208', 'some text', 'C4 text')
, ( 5, 'D70F81C5-8AFF-4ABE-BA64-8F5C1A1C6A90', 'some text', 'C4 text')
, ( 6, '1473E5DC-6F3E-4164-988C-E36EE7C695BE', 'some text', 'C4 text')
, ( 7, '648AEA46-4B45-41F9-AA9B-7129062391B4', 'some text', 'C4 text')
, ( 8, '49497ECB-774D-482D-8230-218E97FB2EB4', 'some text', 'C4 text')
, ( 9, 'B90504FA-CEBA-4383-A61F-82F33DAB7A0E', 'some text', 'C4 text');

INSERT INTO #T2
        ( C1, C2, C3, C4 )
SELECT C1 ,
       C2 ,
       C3 ,
       C4 FROM #T1;

/* Index seek on T1 - no lookup as we include the clustering key */
SELECT C1, C2 FROM #T1 WHERE C2 = '648AEA46-4B45-41F9-AA9B-7129062391B4'

/* Index seek AND key lookup on T1 because there are columns not contained in the PK */
SELECT C1, C2, C3 FROM #T1 WHERE C2 = '648AEA46-4B45-41F9-AA9B-7129062391B4'

/* Clustered index seek on T2 as the predicate is the PK and clustered index */
SELECT C1, C2 FROM #T2 WHERE C2 = '648AEA46-4B45-41F9-AA9B-7129062391B4'

/* Stil a clustered index seek on T2 as the predicate is the PK and clustered index */
SELECT C1, C2, C3 FROM #T2 WHERE C2 = '648AEA46-4B45-41F9-AA9B-7129062391B4'

あなたが考慮に入れ、考える必要があることは次のようになります:

  • ストレージ性能
  • 読み取りと書き込みに関するパフォーマンスの期待
  • アクセス方法(返される列、および述語に使用される列)
  • 使用可能なスペース(クラスター化キーはすべての非クラスター化インデックスに含まれることに注意してください。つまり、より多くのスペースを使用し、キャッシュでより多くを無駄にします。

悲しいことに、これに対する単一の正解はありません。適切な期待を得ることができるだけであり、広範囲のテストを実行して、それらの期待が設計したどのデータモデルでも満たされるかどうかを確認し、満たされない場合は、調整してそれらを改善します。

私が提案したアプローチを使用することに重大な欠点はありますか?

言うのはとても難しいです。唯一の本当の欠点は、ほとんどのクエリが非クラスター化PKを介して行われ、非常に読み取りが重い場合、IOが大幅に増加するため、呼び出しごとのキー検索。

ストレージがそれに耐えることができれば、それは素晴らしいことです。そして、良い面としては、時間の経過とともにインデックスのメンテナンスがひどくなる断片化の問題がないことです(AGを実行していて、メンテナンス期間がない場合は、これらを再構築できないことを意味します)。

3
Nic

クラスタリングキーの選択は1つです。主キーの選択は別のものです。 SQL Serverの既定の動作であるため、PKで自動的にクラスター化しないでください。

  • クラスタリングキーは、他のすべてのインデックスがそれをポイントするため、可能な限り狭くする必要があります。 INT CIXを持つテーブルのINTのインデックスは、レコードごとに8バイトになります。 UNIQUEIDENTIFIER CIXを含むテーブルの場合も同様に、レコードあたり20バイトになります。
  • アップデートを安価に保つには、耐久性がなければなりません。クラスタリングインデックスの変更は、他のすべてのインデックスに伝達する必要があります。
  • 断片化を最小限に抑えるには、常に増加する必要があります(例:NEWSEQUENTIALID())。これがいかに重要であるかについて、相反する報告を見てきました。 Googleの「ホットスポット」と「GUID主キー」には、さまざまな意見があります。 SSDの場合、シリアルデータは以前ほど重要ではありませんが、データが断片化されているため、追加のページルックアップを行うには依然としてコストがかかります。
  • 理想的には、一意である必要があります。そうでない場合、SQLは32ビットの一意名を追加するため、一意のINTがある場合は、一意ではなくクラスタリングインデックスに含めることもできます。
  • クラスタリングキーに対するスキャンでは、個別にホップすることなく、すべてのフィールドを取得できます。多数の範囲スキャンを行うことが予想される場合は、そのフィールドにクラスタリングします。たとえば、OrdersDateOrderedでクラスタ化して、内部分析とレポートのために月別の売上のクエリをサポートするか、またはCustomerIDでクラスタ化して、すべての注文のクエリをサポートします。カスタマーポータルまたはサポートデスクの顧客。
  • primary keyもできるだけ狭くする必要があります。これは、他のテーブルの外部キーが一般的に結合されるためです。スペースの節約は、一般的には良い習慣です。
  • 主キーは、DBAの便宜のために存在します。レコードを一意に識別できるようにするためです。外部キーのターゲットとして

データについて詳しく知らなければ、具体的な推奨を行うことは困難です。 16バイトでは、UNIQUEIDENTIFIERは確かに_IDENTITY INT_より広いですが、2番目のキーを維持するためのコスト(ストレージだけでなく、脳のスペース)も現実的です。テーブルに何億ものレコードがない場合を除き、私は最も単純なソリューションから始めます。単一のUNIQUEIDENTIFIERフィールド上のクラスター化されたPKで、NEWSEQUENTIALID()が入力されています。

2