ナチュラルキーは、代理整数キーよりもSQL Serverで高いまたは低いパフォーマンスを提供しますか?


25

私はサロゲートキーのファンです。私の発見が確認バイアスのリスクがある。

こことhttp://stackoverflow.comの両方で見た多くの質問では、IDENTITY()値に基づく代理キーの代わりに自然キーを使用しています。

コンピューターシステムの私のバックグラウンドは、整数の比較演算を実行すると、文字列を比較するよりも高速になることを教えてくれます。

このコメントは私の信念に疑問を投げかけたので、整数はSQL Serverのキーとして使用する文字列よりも高速であるという仮説を調査するシステムを作成すると思いました。

小さなデータセットでは識別可能な差はほとんどないため、プライマリテーブルに1,000,000行、セカンダリテーブルにプライマリテーブルの各行に10行、合計で10,000,000行の2つのテーブル設定を考えました。二次テーブル。私のテストの前提は、このような2つのテーブルセットを作成することです。1つは自然キーを使用し、もう1つは整数キーを使用し、次のような簡単なクエリでタイミングテストを実行します。

SELECT *
FROM Table1
    INNER JOIN Table2 ON Table1.Key = Table2.Key;

以下は、テストベッドとして作成したコードです。

USE Master;
IF (SELECT COUNT(database_id) FROM sys.databases d WHERE d.name = 'NaturalKeyTest') = 1
BEGIN
    ALTER DATABASE NaturalKeyTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE NaturalKeyTest;
END
GO
CREATE DATABASE NaturalKeyTest 
    ON (NAME = 'NaturalKeyTest', FILENAME = 
        'C:\SQLServer\Data\NaturalKeyTest.mdf', SIZE=8GB, FILEGROWTH=1GB) 
    LOG ON (NAME='NaturalKeyTestLog', FILENAME = 
        'C:\SQLServer\Logs\NaturalKeyTest.mdf', SIZE=256MB, FILEGROWTH=128MB);
GO
ALTER DATABASE NaturalKeyTest SET RECOVERY SIMPLE;
GO
USE NaturalKeyTest;
GO
CREATE VIEW GetRand
AS 
    SELECT RAND() AS RandomNumber;
GO
CREATE FUNCTION RandomString
(
    @StringLength INT
)
RETURNS NVARCHAR(max)
AS
BEGIN
    DECLARE @cnt INT = 0
    DECLARE @str NVARCHAR(MAX) = '';
    DECLARE @RandomNum FLOAT = 0;
    WHILE @cnt < @StringLength
    BEGIN
        SELECT @RandomNum = RandomNumber
        FROM GetRand;
        SET @str = @str + CAST(CHAR((@RandomNum * 64.) + 32) AS NVARCHAR(MAX)); 
        SET @cnt = @cnt + 1;
    END
    RETURN @str;
END;
GO
CREATE TABLE NaturalTable1
(
    NaturalTable1Key NVARCHAR(255) NOT NULL 
        CONSTRAINT PK_NaturalTable1 PRIMARY KEY CLUSTERED 
    , Table1TestData NVARCHAR(255) NOT NULL 
);
CREATE TABLE NaturalTable2
(
    NaturalTable2Key NVARCHAR(255) NOT NULL 
        CONSTRAINT PK_NaturalTable2 PRIMARY KEY CLUSTERED 
    , NaturalTable1Key NVARCHAR(255) NOT NULL 
        CONSTRAINT FK_NaturalTable2_NaturalTable1Key 
        FOREIGN KEY REFERENCES dbo.NaturalTable1 (NaturalTable1Key) 
        ON DELETE CASCADE ON UPDATE CASCADE
    , Table2TestData NVARCHAR(255) NOT NULL  
);
GO

/* insert 1,000,000 rows into NaturalTable1 */
INSERT INTO NaturalTable1 (NaturalTable1Key, Table1TestData) 
    VALUES (dbo.RandomString(25), dbo.RandomString(100));
GO 1000000 

/* insert 10,000,000 rows into NaturalTable2 */
INSERT INTO NaturalTable2 (NaturalTable2Key, NaturalTable1Key, Table2TestData)
SELECT dbo.RandomString(25), T1.NaturalTable1Key, dbo.RandomString(100)
FROM NaturalTable1 T1
GO 10 

CREATE TABLE IDTable1
(
    IDTable1Key INT NOT NULL CONSTRAINT PK_IDTable1 
    PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , Table1TestData NVARCHAR(255) NOT NULL 
    CONSTRAINT DF_IDTable1_TestData DEFAULT dbo.RandomString(100)
);
CREATE TABLE IDTable2
(
    IDTable2Key INT NOT NULL CONSTRAINT PK_IDTable2 
        PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , IDTable1Key INT NOT NULL 
        CONSTRAINT FK_IDTable2_IDTable1Key FOREIGN KEY 
        REFERENCES dbo.IDTable1 (IDTable1Key) 
        ON DELETE CASCADE ON UPDATE CASCADE
    , Table2TestData NVARCHAR(255) NOT NULL 
        CONSTRAINT DF_IDTable2_TestData DEFAULT dbo.RandomString(100)
);
GO
INSERT INTO IDTable1 DEFAULT VALUES;
GO 1000000
INSERT INTO IDTable2 (IDTable1Key)
SELECT T1.IDTable1Key
FROM IDTable1 T1
GO 10

上記のコードは、データベースと4つのテーブルを作成し、テーブルにデータを入力してテストの準備をします。私が実行したテストコードは次のとおりです。

USE NaturalKeyTest;
GO
DECLARE @loops INT = 0;
DECLARE @MaxLoops INT = 10;
DECLARE @Results TABLE (
    FinishedAt DATETIME DEFAULT (GETDATE())
    , KeyType NVARCHAR(255)
    , ElapsedTime FLOAT
);
WHILE @loops < @MaxLoops
BEGIN
    DBCC FREEPROCCACHE;
    DBCC FREESESSIONCACHE;
    DBCC FREESYSTEMCACHE ('ALL');
    DBCC DROPCLEANBUFFERS;
    WAITFOR DELAY '00:00:05';
    DECLARE @start DATETIME = GETDATE();
    DECLARE @end DATETIME;
    DECLARE @count INT;
    SELECT @count = COUNT(*) 
    FROM dbo.NaturalTable1 T1
        INNER JOIN dbo.NaturalTable2 T2 ON T1.NaturalTable1Key = T2.NaturalTable1Key;
    SET @end = GETDATE();
    INSERT INTO @Results (KeyType, ElapsedTime)
    SELECT 'Natural PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;

    DBCC FREEPROCCACHE;
    DBCC FREESESSIONCACHE;
    DBCC FREESYSTEMCACHE ('ALL');
    DBCC DROPCLEANBUFFERS;
    WAITFOR DELAY '00:00:05';
    SET @start = GETDATE();
    SELECT @count = COUNT(*) 
    FROM dbo.IDTable1 T1
        INNER JOIN dbo.IDTable2 T2 ON T1.IDTable1Key = T2.IDTable1Key;
    SET @end = GETDATE();
    INSERT INTO @Results (KeyType, ElapsedTime)
    SELECT 'IDENTITY() PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;

    SET @loops = @loops + 1;
END
SELECT KeyType, FORMAT(CAST(AVG(ElapsedTime) AS DATETIME), 'HH:mm:ss.fff') AS AvgTime 
FROM @Results
GROUP BY KeyType;

結果は次のとおりです。

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

ここで何か間違ったことをしていますか、またはINTキーは25文字のナチュラルキーの3倍高速ですか?

注意してください、私はここでフォローアップの質問を書きまし


1
INTは4バイトで、有効なNVARCHAR(25)は約14倍長い(長さなどのシステムデータを含む)ので、インデックスだけでは、PKインデックスの幅が非常に広く、より深くなると思います。処理時間に影響を与える/ Oが必要です。ただし、自然な整数(おそらくチェックデジット)は、代理ID列に使用することを考えているINTとほとんど同じです。そのため、「ナチュラルキー」はINT、BIGINT、CHAR、NVARCHARである可能性があり、それはすべて重要です。
RLF

7
@ MikeSherrill'Catcall 'が得ていたパフォーマンスの向上は、自然キーを使用する場合、実際には「ルックアップ」テーブルに対する結合が必要ないことです。結合を使用してルックアップ値を取得するクエリと、値が既にメインテーブルに格納されているクエリを比較します。ルックアップテーブルの自然キーの長さと行数に応じて、異なる「勝者」が得られる場合があります。
ミカエルエリクソン

3
@MikaelErikssonが言ったことに加えて、サロゲートではテーブルAからDを介してBとCを結合する必要があり、自然キーではAからDを直接結合できる2つ以上のテーブル(4つなど)間の結合がある場合
ypercubeᵀᴹ

回答:


18

一般に、SQL ServerはインデックスにBツリーを使用します。インデックスシークの費用は、このストレージ形式のキーの長さに直接関係しています。したがって、通常、代理キーは、インデックスシークの自然キーよりも優れています。

SQL Serverは、デフォルトで主キーのテーブルをクラスター化します。クラスター化インデックスキーは行を識別するために使用されるため、他のすべてのインデックスに含まれる列として追加されます。キーが広いほど、すべてのセカンダリインデックスが大きくなります。

さらに悪いことに、セカンダリインデックスが明示的に定義されUNIQUEていない場合、クラスター化インデックスキーはそれらの各キーの一部に自動的になります。これは通常、ほとんどのインデックスに当てはまります。通常、インデックスは、一意性を強制する必要がある場合にのみ一意として宣言されます。

そのため、質問が自然対サロゲートクラスターインデックスの場合、サロゲートがほぼ常に勝ちます。

一方、その代理列をテーブルに追加すると、テーブル自体が大きくなります。これにより、クラスター化インデックススキャンのコストが高くなります。したがって、セカンダリインデックスが非常に少なく、ワークロードがすべての(またはほとんどの)行を頻繁に調べる必要がある場合、実際には、これらのいくつかの余分なバイトを節約する自然なキーを使用する方がよい場合があります。

最後に、自然キーは多くの場合、データモデルの理解を容易にします。より多くのストレージスペースを使用している間、自然なプライマリキーは自然な外部キーにつながり、それがローカル情報密度を増加させます。

したがって、データベースの世界ではよくあることですが、本当の答えは「依存する」です。そして-現実的なデータを使用して、常に独自の環境でテストしてください。


10

最高のものは真ん中にあると信じています

ナチュラルキーの概要:

  1. データモデルは、誰かの頭ではなくサブジェクトエリアから取得されるため、より明確になります。
  2. シンプルなキー(1列、間CHAR(4)およびCHAR(20))は、いくつかの余分なバイトを保存されていますが、(彼らの一貫性を監視する必要がON UPDATE CASCADE変更される可能性がありますそれらのキーのために重要になります)。
  3. 自然キーが複雑な場合の多くの場合:2つ以上の列で構成されます。そのようなキーが前のキーとして別のエンティティに移行する可能性がある場合、データのオーバーヘッドが増加し(インデックスとデータ列が大きくなる可能性があります)、パフォーマンスが低下します。
  4. キーが大きな文字列の場合、単純な検索条件がデータベースエンジンでバイト配列比較になり、ほとんどの場合整数比較よりも遅いため、常に整数キーになります。
  5. キーが多言語文字列の場合、照合も監視する必要があります。

利点: 1および2。

ウォッチアウト: 3、4、5。


人工IDキーの概要:

  1. この機能はデータベースエンジンによって処理されるため、(ほとんどの場合)作成と処理について気にする必要はありません。これらはデフォルトで一意であり、多くのスペースを必要としません。ON UPDATE CASCADEキー値が変更されないため、カスタム操作は省略される場合があります。

  2. それらは、多くの場合、外部キーとしての移行に最適な候補です。

    2.1。1つの列で構成されます。

    2.2。重みが小さく、比較演算のために高速に動作する単純なタイプを使用します。

  3. どのキーもどこにも移行されないアソシエーションエンティティの場合、有用性が失われるため、純粋なデータオーバーヘッドになる可能性があります。複雑な自然主キー(文字列列がない場合)の方が便利です。

利点: 1および2。

ウォッチアウト: 3。


結論:

人工キーは、この機能用に設計されているため、保守性、信頼性、および高速性が向上しています。ただし、場合によっては不要です。たとえばCHAR(4)、ほとんどの場合、単一列の候補はのように動作しINT IDENTITYます。ここにも別の質問があります:保守 + 安定性または自明性

質問「人工キーを注入すべきかどうか」常に自然なキー構造に依存します:

  • 大きな文字列が含まれている場合は、低速であり、外部から別のエンティティに移行する場合にデータのオーバーヘッドが追加されます。
  • 複数の列で構成されている場合、他のエンティティに外部として移行する場合、速度が遅くなり、データのオーバーヘッドが追加されます。

5
「キー値が変更されないため、ON UPDATE CASCADEなどのカスタム操作は省略される場合があります。」代理キーの効果は、すべての外部キー参照を「ON UPDATE CASCADE」と同等にすることです。キーは変更されませんが、それが表す値はありません
マイクシェリル 'キャットリコール'

@ MikeSherrill'Catcall 'はい、もちろん。ただし、ON UPDATE CASCADEキーは更新されませんが、使用されません。ただし、ON UPDATE NO ACTION構成されている場合、構成されていると問題になる可能性があります。つまり、DBMSはキー列の値を変更せずに、DBMSを使用しません。
BlitZ

4

キーはデータベースの論理機能であり、パフォーマンスは常にストレージの物理的な実装と、その実装に対して実行される物理的な操作によって決まります。したがって、パフォーマンス特性をキーに帰するのは間違いです。

ただし、この特定の例では、テーブルとクエリの2つの可能な実装が相互に比較されます。この例では、タイトルで提示されている質問には答えていません。行われている比較は、1つのタイプのインデックス(Bツリー)を使用する2つの異なるデータ型(整数と文字)を使用した結合です。「明らかな」点は、ハッシュインデックスまたは他のタイプのインデックスが使用された場合、2つの実装間で測定可能なパフォーマンスの違いはほとんどないということです。ただし、この例にはさらに根本的な問題があります。

2つのクエリのパフォーマンスが比較されていますが、2つのクエリは異なる結果を返すため、論理的に同等ではありません!より現実的なテストでは、異なる実装を使用して同じ結果を返す2つのクエリを比較します。

代理キーに関する重要な点は、テーブル内の余分な属性であり、テーブルにはビジネスドメインで使用される「意味のある」キー属性もあるということです。クエリ結果が役立つのは、非代理属性です。したがって、現実的なテストでは、自然キーのみを使用してテーブルを比較し、同じテーブル内に自然キー代理キーの両方がある代替実装を使用します。代理キーには通常、追加のストレージとインデックスが必要であり、定義により追加の一意性制約が必要です。サロゲートには、外部ナチュラルキー値をサロゲートにマッピングするための追加処理が必要です。

次に、この潜在的なクエリを比較します。

A.

SELECT t2.NaturalTable2Key, t2.NaturalTable1Key
FROM Table2 t2;

Table2のNaturalTable1Key属性がサロゲートIDTable1Keyに置き換えられた場合、その論理的同等物に次のようになります。

B.

SELECT t2.NaturalTable2Key, t1.NaturalTable1Key
FROM Table2 t2
INNER JOIN Table1 t1
ON t1.IDTable1Key = t2.IDTable1Key;

クエリBには結合が必要です。クエリAはサポートしていません。これは、サロゲートを(過剰に)使用するデータベースではおなじみの状況です。クエリは不必要に複雑になり、最適化がはるかに難しくなります。ビジネスロジック(特にデータ整合性の制約)は、実装、テスト、および検証がより困難になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.