web-dev-qa-db-ja.com

自然キーは、サロゲート整数キーよりもSQL Serverで高いまたは低いパフォーマンスを提供しますか?

私は代理キーのファンです。私の調査結果が確認バイアスされているリスクがあります。

ここと 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;

これらは結果です:

enter image description here

私はここで何か間違っているのですか、それともINTキーは25文字の自然キーより3倍高速ですか?

注、私はフォローアップの質問 here を書きました。

27
Max Vernon

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

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

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

したがって、質問が自然なクラスタリングインデックスとサロゲートクラスタードインデックスの場合、サロゲートがほぼ常に勝ちます。

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

最後に、自然キーを使用すると、データモデルを理解しやすくなります。より多くのストレージスペースを使用している間、自然な主キーは自然な外部キーにつながり、ローカルの情報密度が増加します。

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

20
Sebastian Meine

最高は真ん中にあると思います

ナチュラルキーの概要:

  1. それらは、誰かの頭からではなく、主題領域から来ているので、データモデルをより明白にします。
  2. 単純なキー(CHAR(4)CHAR(20)の間の1つの列)は余分なバイトを節約していますが、それらの一貫性を監視する必要があります(_ON UPDATE CASCADE_はこれらのキーにとって重要になります)。変更される場合があります)。
  3. 自然キーが複雑な場合の多くの場合:2つ以上の列で構成されます。そのようなキーがフォアキーとして別のエンティティに移行する可能性がある場合、データオーバーヘッドが追加され(インデックスとデータ列が大きくなる可能性があります)、パフォーマンスが低下します。
  4. Keyが大きな文字列である場合、単純な検索条件はデータベースエンジンのバイト配列比較になり、ほとんどの場合整数比較よりも遅いため、おそらくそれは常に整数キーに緩みます。
  5. キーが多言語文字列の場合、照合も監視する必要があります。

メリット: 1および2。

監視: 3、4、5。


人工識別キーの概要:

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

  2. 以下の理由により、これらは(多くの場合)外部キーとしての移行に最適です。

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

    2.2。単純な型を使用して、重みが小さく、比較演算で高速に動作します。

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

メリット: 1および2。

監視: 3。


結論:

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

質問「人工的な鍵を注入すべきかどうか」常に依存自然な鍵の構造:

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

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

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

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

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

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

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ではありません。これは、サロゲートを(過剰に)使用するデータベースではよくある状況です。クエリは不必要に複雑になり、最適化がはるかに困難になります。ビジネスロジック(特にデータ整合性制約)は、実装、テスト、および検証がより困難になります。

5
nvogel