web-dev-qa-db-ja.com

複合主キー内で定義された列の非クラスター化インデックス

マルチテナントデータベースを使用していて、すべてのテーブル内の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つのオプションのうち、どのタイプのインデックスの組み合わせがスケーリングに最も適していると見なされますか?複合主キーのみ、または複合主キーと追加の非クラスター化インデックス?またはより実行可能な別のオプションはありますか?

3
PicoDeGallo

サンプルクエリが推奨事項の基になる公正な例であるかどうかはわかりません。サンプルクエリは特定のクライアントに固有ではないため、典型的なマルチテナントapplicationクエリではありません。これは、すべての(または少なくとも複数の)顧客についての洞察を得ることを目的とした、サポートまたは管理クエリのようなものです。もちろん、メンテナンスに関連する場合もあります(たとえば、ガベージコレクションは最も古い日付を探し、AccountIdは考慮しません)。これを分離してみましょう:

  1. 一般

    クラスタ化インデックスの先頭/左端のキー列のみである非クラスタ化インデックスを使用するメリットはありません。クラスター化インデックスは既にその順序になっているため、統計が存在します。したがって、IX_AccountState_Accountオプション2のインデックスは完全に無駄であり、システムに抵抗があります。

  2. サポート/管理/メンテナンスクエリ

    これらのクエリ、特にメンテナンスクエリは、AccountId値全体で機能しますcan。そのため、一部のクエリでは、クラスター化インデックスキー列AccountIdではない非クラスター化インデックスのメリットが確実に得られます(または、より一般的に言えば、左端/先頭のキー列ではありません)。これは、エンティティID onlyでフィルタリング/ソートするクエリがあることを前提としています。このようなクエリがない場合は、おそらくこのインデックスは必要ありません。

  3. アプリケーションクエリ

    これらのクエリは常にAccountIdを含める必要があるため、エンティティIDのみに基づくインデックスがどのように役立つかはわかりません。

    私はあなたが説明しているものと同様のマルチテナントシステムで作業しましたが、今までにないことを考えただけです。クラスター化インデックスの統計情報は、キー列の組み合わせではなく、左端/先行列に基づいているためです、それらのオブジェクトの統計を手動で作成することはかもしれない有益です。すべてのキー列(先頭列だけでなく)を占めるのは密度の部分にすぎないことを思い出しましたが、それはクエリの最適化に役立つ可能性があります。私は試していないので、これをテストする必要があります(そして、すぐにそのようなテストを行うことはできません)。

2
Solomon Rutzky