マルチテナントデータベースを使用していて、すべてのテーブル内のAccountId
列をテナント分離用の複合主キーの一部として利用しています。複合主キーの一部である各列に追加の非クラスター化インデックスを作成して、SQL Serverが正確な統計を維持し、ルックアップテーブルに結合するときのクエリパフォーマンスを向上させることは有益ですか?
たとえば、Account
とオフィスがあるアメリカ人State
の間の1対多の関係を定義する連想テーブル内では、理論的には次の構造とサンプルクエリから、2つのオプションのどちらが望ましいですか?
Account
およびState
テーブルを作成し、サンプルデータを入力します。
DROP TABLE IF EXISTS [dbo].[Account];
DROP TABLE IF EXISTS [dbo].[State];
-- [Account] table and sample values.
IF OBJECT_ID('[dbo].[Account]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Account] (
[AccountId] [int] IDENTITY(1,1) NOT NULL
,[AccountAlias] [varchar](3) NOT NULL
,[AccountName] [varchar](128) NOT NULL
,CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED ([AccountId] ASC)
,CONSTRAINT [UQ_Account_Alias] UNIQUE NONCLUSTERED ([AccountAlias] ASC)
,CONSTRAINT [UQ_Account_Name] UNIQUE NONCLUSTERED ([AccountName] ASC)
);
SET IDENTITY_INSERT [dbo].[Account] ON;
INSERT INTO [dbo].[Account] ([AccountId], [AccountAlias], [AccountName])
VALUES (1, 'SA1', 'Sample Account 1'), (2, 'SA2', 'Sample Account 2'), (3, 'SA3', 'Sample Account 3')
SET IDENTITY_INSERT [dbo].[Account] OFF;
END;
GO
-- [State] table and sample values.
IF OBJECT_ID('[dbo].[State]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[State] (
[StateId] [tinyint] IDENTITY(1,1) NOT NULL
,[StateCode] [varchar](2) NOT NULL
,[StateName] [varchar](32) NOT NULL
,CONSTRAINT [PK_State] PRIMARY KEY CLUSTERED ([StateId] ASC)
,CONSTRAINT [UQ_State_Code] UNIQUE NONCLUSTERED ([StateCode] ASC)
,CONSTRAINT [UQ_State_Name] UNIQUE NONCLUSTERED ([StateName] ASC)
);
SET IDENTITY_INSERT [dbo].[State] ON;
INSERT INTO [dbo].[State] ([StateId], [StateCode], [StateName])
VALUES (1, 'AL', 'Alabama'), (2, 'AK', 'Alaska'), (3, 'AZ', 'Arizona'), (4, 'AR', 'Arkansas'), (5, 'CA', 'California')
SET IDENTITY_INSERT [dbo].[State] OFF;
END;
GO
作成AccountState
オプション1-複合主キーのみ
DROP TABLE IF EXISTS [dbo].[AccountState];
IF OBJECT_ID('[dbo].[AccountState]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[AccountState] (
[AccountId] [int] NOT NULL
,[StateId] [tinyint] NOT NULL
,CONSTRAINT [PK_AccountState] PRIMARY KEY CLUSTERED ([AccountId] ASC, [StateId] ASC)
,CONSTRAINT [FK_AccountState_Account] FOREIGN KEY ([AccountId]) REFERENCES [dbo].[Account]([AccountId])
,CONSTRAINT [FK_AccountState_State] FOREIGN KEY ([StateId]) REFERENCES [dbo].[State]([StateId])
);
INSERT INTO [dbo].[AccountState] ([AccountId], [StateId])
SELECT A.[AccountId], S.[StateId]
FROM [dbo].[Account] A CROSS JOIN [dbo].[State] S
WHERE A.[AccountId] = 1 AND S.[StateId] IN (1, 2, 3)
UNION
SELECT A.[AccountId], S.[StateId]
FROM [dbo].[Account] A CROSS JOIN [dbo].[State] S
WHERE A.[AccountId] = 2 AND S.[StateId] IN (3, 4, 5)
UNION
SELECT A.[AccountId], S.[StateId]
FROM [dbo].[Account] A CROSS JOIN [dbo].[State] S
WHERE A.[AccountId] = 3 AND S.[StateId] IN (1, 3, 5)
END;
GO
Create AccountState
OPTION 2-複合主キー+非クラスター化インデックス
DROP TABLE IF EXISTS [dbo].[AccountState];
IF OBJECT_ID('[dbo].[AccountState]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[AccountState] (
[AccountId] [int] NOT NULL
,[StateId] [tinyint] NOT NULL
,CONSTRAINT [PK_AccountState] PRIMARY KEY CLUSTERED ([AccountId] ASC, [StateId] ASC)
,CONSTRAINT [FK_AccountState_Account] FOREIGN KEY ([AccountId]) REFERENCES [dbo].[Account]([AccountId])
,CONSTRAINT [FK_AccountState_State] FOREIGN KEY ([StateId]) REFERENCES [dbo].[State]([StateId])
,INDEX [IX_AccountState_Account] NONCLUSTERED ([AccountId])
,INDEX [IX_AccountState_State] NONCLUSTERED ([StateId])
);
INSERT INTO [dbo].[AccountState] ([AccountId], [StateId])
SELECT A.[AccountId], S.[StateId]
FROM [dbo].[Account] A CROSS JOIN [dbo].[State] S
WHERE A.[AccountId] = 1 AND S.[StateId] IN (1, 2, 3)
UNION
SELECT A.[AccountId], S.[StateId]
FROM [dbo].[Account] A CROSS JOIN [dbo].[State] S
WHERE A.[AccountId] = 2 AND S.[StateId] IN (3, 4, 5)
UNION
SELECT A.[AccountId], S.[StateId]
FROM [dbo].[Account] A CROSS JOIN [dbo].[State] S
WHERE A.[AccountId] = 3 AND S.[StateId] IN (1, 3, 5)
END;
GO
サンプルクエリ
SELECT
A.[AccountName]
,S.[StateName]
FROM
[dbo].[AccountState] A_S
JOIN [dbo].[Account] A
ON A_S.[AccountId] = A.[AccountId]
JOIN [dbo].[State] S
ON A_S.[StateId] = S.[StateId]
WHERE
S.[StateCode] = 'CA'
2つのオプションのうち、どのタイプのインデックスの組み合わせがスケーリングに最も適していると見なされますか?複合主キーのみ、または複合主キーと追加の非クラスター化インデックス?またはより実行可能な別のオプションはありますか?
サンプルクエリが推奨事項の基になる公正な例であるかどうかはわかりません。サンプルクエリは特定のクライアントに固有ではないため、典型的なマルチテナントapplicationクエリではありません。これは、すべての(または少なくとも複数の)顧客についての洞察を得ることを目的とした、サポートまたは管理クエリのようなものです。もちろん、メンテナンスに関連する場合もあります(たとえば、ガベージコレクションは最も古い日付を探し、AccountId
は考慮しません)。これを分離してみましょう:
一般
クラスタ化インデックスの先頭/左端のキー列のみである非クラスタ化インデックスを使用するメリットはありません。クラスター化インデックスは既にその順序になっているため、統計が存在します。したがって、IX_AccountState_Account
オプション2のインデックスは完全に無駄であり、システムに抵抗があります。
サポート/管理/メンテナンスクエリ
これらのクエリ、特にメンテナンスクエリは、AccountId
値全体で機能しますcan。そのため、一部のクエリでは、クラスター化インデックスキー列AccountId
ではない非クラスター化インデックスのメリットが確実に得られます(または、より一般的に言えば、左端/先頭のキー列ではありません)。これは、エンティティID onlyでフィルタリング/ソートするクエリがあることを前提としています。このようなクエリがない場合は、おそらくこのインデックスは必要ありません。
アプリケーションクエリ
これらのクエリは常にAccountId
を含める必要があるため、エンティティIDのみに基づくインデックスがどのように役立つかはわかりません。
私はあなたが説明しているものと同様のマルチテナントシステムで作業しましたが、今までにないことを考えただけです。クラスター化インデックスの統計情報は、キー列の組み合わせではなく、左端/先行列に基づいているためです、それらのオブジェクトの統計を手動で作成することはかもしれない有益です。すべてのキー列(先頭列だけでなく)を占めるのは密度の部分にすぎないことを思い出しましたが、それはクエリの最適化に役立つ可能性があります。私は試していないので、これをテストする必要があります(そして、すぐにそのようなテストを行うことはできません)。