私は代理キーのファンです。私の調査結果が確認バイアスされているリスクがあります。
ここと 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倍高速ですか?
注、私はフォローアップの質問 here を書きました。
一般に、SQL Serverは B + Trees をインデックスに使用します。インデックスシークの費用は、このストレージ形式のキーの長さに直接関連しています。したがって、サロゲートキーは通常、インデックスシークの自然キーよりも優れています。
SQL Serverは、デフォルトで主キーのテーブルをクラスター化します。クラスタ化インデックスキーは行の識別に使用されるため、他のすべてのインデックスに含まれる列として追加されます。そのキーが広いほど、すべてのセカンダリインデックスが大きくなります。
さらに悪いことに、セカンダリインデックスがUNIQUE
として明示的に定義されていない場合、クラスター化インデックスキーは自動的にそれぞれのキーの一部になります。通常、インデックスは一意性を強制することが要件である場合にのみ一意として宣言されるため、これは通常、ほとんどのインデックスに適用されます。
したがって、質問が自然なクラスタリングインデックスとサロゲートクラスタードインデックスの場合、サロゲートがほぼ常に勝ちます。
一方、そのサロゲート列をテーブルに追加して、テーブル自体を大きくしています。これにより、クラスター化インデックススキャンのコストが高くなります。したがって、セカンダリインデックスがほとんどなく、ワークロードがすべて(またはほとんど)の行を頻繁に確認する必要がある場合、実際には、いくつかの余分なバイトを節約する自然なキーを使用する方が良いでしょう。
最後に、自然キーを使用すると、データモデルを理解しやすくなります。より多くのストレージスペースを使用している間、自然な主キーは自然な外部キーにつながり、ローカルの情報密度が増加します。
したがって、データベースの世界ではよくあることですが、本当の答えは「依存する」ことです。そして、常に現実的なデータを使用して、自分の環境でテストしてください。
最高は真ん中にあると思います。
ナチュラルキーの概要:
CHAR(4)
とCHAR(20)
の間の1つの列)は余分なバイトを節約していますが、それらの一貫性を監視する必要があります(_ON UPDATE CASCADE
_はこれらのキーにとって重要になります)。変更される場合があります)。メリット: 1および2。
監視: 3、4、5。
人工識別キーの概要:
この機能はデータベースエンジンによって処理されるため、それらの作成と処理(ほとんどの場合)に煩わされる必要はありません。それらはデフォルトで固有であり、それほど多くのスペースを取りません。キー値が変更されないため、_ON UPDATE CASCADE
_のようなカスタム操作は省略される場合があります。
以下の理由により、これらは(多くの場合)外部キーとしての移行に最適です。
2.1。 1つの列で構成されます。
2.2。単純な型を使用して、重みが小さく、比較演算で高速に動作します。
キーがどこにも移行されない関連エンティティの場合、有用性が失われるため、純粋なデータオーバーヘッドになる可能性があります。複雑な自然主キー(そこに文字列列がない場合)の方が便利です。
メリット: 1および2。
監視: 3。
結論:
人工キーは、この機能用に設計されているため、保守性が高く、信頼性が高く、高速です。ただし、場合によっては不要です。たとえば、ほとんどの場合、単一のCHAR(4)
列の候補は_INT IDENTITY
_のように動作します。したがって、ここにも別の質問があります:maintainability+stabilityまたは自明性?
質問「人工的な鍵を注入すべきかどうか」常に依存自然な鍵の構造:
キーはデータベースの論理機能ですが、パフォーマンスは常にストレージ内の物理的な実装とその実装に対して実行される物理的な操作によって決まります。したがって、パフォーマンス特性をキーに起因させるのは誤りです。
ただし、この特定の例では、テーブルとクエリの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ではありません。これは、サロゲートを(過剰に)使用するデータベースではよくある状況です。クエリは不必要に複雑になり、最適化がはるかに困難になります。ビジネスロジック(特にデータ整合性制約)は、実装、テスト、および検証がより困難になります。