web-dev-qa-db-ja.com

分割テーブルに追加の一意のキーを作成する

TenantId列に基づいてデータを論理的に分離するために、パーティション構成を使用しています。 TenantIdは、テナントの分離が必要なすべてのテーブルのパーティション列とクラスタリングインデックスです。 TenantAliasTenantNameも保持する[Tenant]テーブルに、一意の非クラスター化インデックスを作成したいと思います。パーティション主キーとしてパーティション列の一部でない限り、パーティションスキームに一意のインデックスを作成できません。この事実により、パフォーマンスへの影響(特により大きなコンテキスト)について心配するようになりましたが、これは見当違いの考えかもしれません。

潜在的なパフォーマンスへの影響があったとしても、TenantIdTenantAlias、およびTenantNameの組み合わせは常に一意のコンポジションになるため、非パーティションを含めることになりましたクラスター化インデックスに列を追加します。ただし、それが可能かどうか、複合キーにnotを含めることに不利益があるかどうか、またはそれらをパーティションスキームに配置しないようにする方がよいかどうかを知りたいですすべて。

最適なコンテキストのために、[IsolationExample]というデータベースにセキュリティスキーマ、ファイルグループ、パーティション関数、パーティション構成を設定してみましょう。

IF (SCHEMA_ID('Auth') IS NULL)
BEGIN
    EXEC ('CREATE SCHEMA [Auth] AUTHORIZATION [dbo]')
END;
GO

ALTER DATABASE [IsolationExample]
ADD FILEGROUP [Auth]
GO

ALTER DATABASE [IsolationExample]
ADD FILE (
    NAME = N'IsolationExample_Auth', FILENAME = N'E:\MSSQL\Data\IsolationExample_Auth.ndf', SIZE = 512MB , MAXSIZE = UNLIMITED, FILEGROWTH = 512MB
    )
TO FILEGROUP [Auth]
GO

IF EXISTS (SELECT 1 FROM sys.partition_schemes WHERE [name] = 'PS_Tenant_Isolation') DROP PARTITION SCHEME [PS_Tenant_Isolation];
IF EXISTS (SELECT 1 FROM sys.partition_functions WHERE [name] = 'PF_Tenant_Isolation') DROP PARTITION FUNCTION [PF_Tenant_Isolation];

IF NOT EXISTS (SELECT 1 FROM sys.partition_functions WHERE [name] = 'PF_Tenant_Isolation')
BEGIN
CREATE PARTITION FUNCTION [PF_Tenant_Isolation] ([int])
    -- RANGE RIGHT values will be SPLIT based on external logic to add the correct values (1,2,3 ...) that corresponds with [Tenant].[TenantId].
    AS RANGE RIGHT FOR VALUES (0);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.partition_schemes WHERE [name] = 'PS_Tenant_Isolation')
BEGIN
CREATE PARTITION SCHEME [PS_Tenant_Isolation]
    AS PARTITION [PF_Tenant_Isolation]
    ALL TO ([Auth]);
END;
GO

オプション1

最初に試行されたDDL操作は機能しません。

DROP TABLE IF EXISTS [Auth].[Tenant];
IF OBJECT_ID('[Auth].[Tenant]', 'U') IS NULL
BEGIN
    CREATE TABLE [Auth].[Tenant] (
        [TenantId] [int] IDENTITY(1,1) NOT NULL
        ,[TenantAlias] [varchar](3) NOT NULL
        ,[TenantName] [varchar](256) NOT NULL
        ,CONSTRAINT [PK_Tenant_TenantId] PRIMARY KEY CLUSTERED ([TenantId] ASC)
        ,CONSTRAINT [UQ_Tenant_TenantAlias] UNIQUE NONCLUSTERED ([TenantAlias] ASC)
        ,CONSTRAINT [UQ_Tenant_TenantName] UNIQUE NONCLUSTERED ([TenantName] ASC)
    ) ON [PS_Tenant_Isolation]([TenantId]);
END;
GO

列 'TenantId'は、インデックス 'UQ_Tenant_TenantName'のパーティション列です。一意のインデックスのパーティション列は、インデックスキーのサブセットである必要があります。

オプション2

DDL操作をCREATE TABLE操作のスコープ外に配置すると、同じエラーが発生します。

DROP TABLE IF EXISTS [Auth].[Tenant];
IF OBJECT_ID('[Auth].[Tenant]', 'U') IS NULL
BEGIN
    CREATE TABLE [Auth].[Tenant] (
        [TenantId] [int] IDENTITY(1,1) NOT NULL
        ,[TenantAlias] [varchar](3) NOT NULL
        ,[TenantName] [varchar](256) NOT NULL
        ,CONSTRAINT [PK_Tenant_TenantId] PRIMARY KEY CLUSTERED ([TenantId] ASC)
    ) ON [PS_Tenant_Isolation]([TenantId]);
    CREATE UNIQUE NONCLUSTERED INDEX [UQ_Tenant_TenantAlias] ON [Auth].[Tenant]([TenantAlias] ASC) ON [PS_Tenant_Isolation]([TenantId]);
    CREATE UNIQUE NONCLUSTERED INDEX [UQ_Tenant_TenantName] ON [Auth].[Tenant]([TenantName] ASC) ON [PS_Tenant_Isolation]([TenantId]);
END;
GO

列 'TenantId'は、インデックス 'UQ_Tenant_TenantName'のパーティション列です。一意のインデックスのパーティション列は、インデックスキーのサブセットである必要があります。

*オプション3

パーティションスキームに一意のインデックスを配置しない場合、DDL操作は成功しますが、予想どおり、指定されたファイルグループに直接インデックスが配置されます。私の考えでは、パーティション列のパフォーマンス上の利点を活用できないため、これはパーティション構成の目的全体を無効にします。 しかし、私はこれについて間違っている可能性があり、そうであれば修正したいと思います。

DROP TABLE IF EXISTS [Auth].[Tenant];
IF OBJECT_ID('[Auth].[Tenant]', 'U') IS NULL
BEGIN
    CREATE TABLE [Auth].[Tenant] (
        [TenantId] [int] IDENTITY(1,1) NOT NULL
        ,[TenantAlias] [varchar](3) NOT NULL
        ,[TenantName] [varchar](256) NOT NULL
        ,CONSTRAINT [PK_Tenant_TenantId] PRIMARY KEY CLUSTERED ([TenantId] ASC)
    ) ON [PS_Tenant_Isolation]([TenantId]);
    CREATE UNIQUE NONCLUSTERED INDEX [UQ_Tenant_TenantAlias] ON [Auth].[Tenant]([TenantAlias] ASC) ON [Auth];
    CREATE UNIQUE NONCLUSTERED INDEX [UQ_Tenant_TenantName] ON [Auth].[Tenant]([TenantName] ASC) ON [Auth];
END;
GO

Tenant Isolation - Option 3

オプション4-複合キー

最後に、それらを複合キーとして含めると、DDL操作が機能しそれらがパーティション構成に配置されます。これは、インデックスがパーティション構成にある "望ましい"終了動作ですが、クラスター化インデックスの複合キーとしてそれらを含めることが適切かどうかを知りたいです。

DROP TABLE IF EXISTS [Auth].[Tenant];
IF OBJECT_ID('[Auth].[Tenant]', 'U') IS NULL
BEGIN
    CREATE TABLE [Auth].[Tenant] (
        [TenantId] [int] IDENTITY(1,1) NOT NULL
        ,[TenantAlias] [varchar](3) NOT NULL
        ,[TenantName] [varchar](256) NOT NULL
        ,CONSTRAINT [PK_Tenant_TenantId] PRIMARY KEY CLUSTERED ([TenantId] ASC, [TenantAlias] ASC, [TenantName] ASC)
    ) ON [PS_Tenant_Isolation]([TenantId]);
END;
GO

オプション3またはオプション4、あるいはおそらく私が知らないオプション5を提供するほうがよいですか?

また、純粋にこの[Tenant]テーブルのコンテキスト外で考え、追加の一意のキーを必要とする他のテーブルにも同じ一般的な理解を伝えたいと思います。たとえば、数十億行になる可能性のある[Document]テーブルには、TenantIdに加えて、DocumentIdのbigint ID列と一意のDocumentBatchNameがあります。 。

3
PicoDeGallo

私にとっての質問は、何をパーティション化しようとしているのか、何をユニークにしようとしているのかということです。

オプション4では、エイリアスと名前を主キーに追加すると、_ID, Alias, Name_タプルのみが実際に一意であるという厄介な状況に陥ります。

_CREATE TABLE [Auth].[Tenant] (
    [TenantId] [int] IDENTITY(1,1) NOT NULL
    ,[TenantAlias] [varchar](3) NOT NULL
    ,[TenantName] [varchar](256) NOT NULL
    ,[Other] INTEGER
    ,CONSTRAINT [PK_Tenant_TenantId] PRIMARY KEY CLUSTERED ([TenantId] ASC, [TenantAlias] ASC, [TenantName] ASC)
) ON [PS_Tenant_Isolation]([TenantId]);

INSERT  INTO Auth.Tenant ( TenantAlias, TenantName )
VALUES  ( 'A', 'Apple' ),
        ( 'A', 'Apple' ),
        ( 'B', 'Banana' );

SELECT  *
FROM    Auth.Tenant 
_

上記のOther列がテナントデータを含む一連の列を表すシナリオでは、すべての場所のクラスター化されたキーで完全に不要な大量のデータを複製し、TenantNameをそのインデックスの一部にします。サイズの問題についてはうめき声はさておき、次のようにして、はるかに大きな問題を抱えています。

_UPDATE  Auth.Tenant
    SET TenantName = 'Orange'
WHERE   TenantID = 2;

SELECT  *
FROM    Auth.Tenant 

DROP TABLE Auth.Tenant;
_

現時点では、これは非常に弱いモデルです。

一意性の要件に対処し、パーティション分割の利点も得るために、UNIQUEファイルグループでAuthの作業を実行し、次に_PS_Tenant_Isolation_関数を使用してデータを個別にパーティション分割します。

_IF ( OBJECT_ID( 'Auth.Tenant', 'U' ) IS NULL )
BEGIN
    --DROP TABLE Auth.Tenant;
    CREATE TABLE Auth.Tenant
    (
        TenantID                INTEGER IDENTITY( 1, 1 ) NOT NULL,
        TenantAlias             VARCHAR( 3 ) NOT NULL,
        TenantName              VARCHAR( 256 ) NOT NULL
    );

    ALTER TABLE Auth.Tenant
    ADD CONSTRAINT PK__Tenant
        PRIMARY KEY CLUSTERED ( TenantID )
    ON  [Auth];

    ALTER TABLE Auth.Tenant
    ADD CONSTRAINT UQ__Tenant__TenantAlias
        UNIQUE ( TenantAlias )
    ON  [Auth];

    ALTER TABLE Auth.Tenant
    ADD CONSTRAINT UQ__Tenant_TenantName
        UNIQUE ( TenantName )
    ON  [Auth];

    INSERT  INTO Auth.Tenant ( TenantAlias, TenantName )
    VALUES  ( 'A', 'Apple' ),
            ( 'B', 'Banana' );
END;
GO

IF ( OBJECT_ID( 'Auth.TenantData', 'U' ) IS NULL )
BEGIN
    --DROP TABLE Auth.TenantData;
    CREATE TABLE Auth.TenantData
    (
        TenantDataID            INTEGER IDENTITY( 1, 1 ) NOT NULL,
        TenantID                INTEGER NOT NULL,
        Other                   BIT
    );

    ALTER TABLE Auth.TenantData
    ADD CONSTRAINT PK__TenantData
        PRIMARY KEY CLUSTERED ( TenantID, TenantDataID )
    ON  [PS_Tenant_Isolation]( TenantID );

    ALTER TABLE Auth.TenantData
    ADD CONSTRAINT FK__TenantData__Tenant
        FOREIGN KEY ( TenantID )
        REFERENCES Auth.Tenant ( TenantID );

    INSERT  INTO Auth.TenantData ( TenantID, Other )
    VALUES  ( 1, 1 ),
            ( 2, 0 );
END;
GO

SELECT  *
FROM    Auth.Tenant;

SELECT  *
FROM    Auth.TenantData;

SELECT  ObjectName = OBJECT_NAME( ps.object_id ), 
        IndexName = i.name,
        DataSpaceName = ds.name,
        ps.partition_number, ps.row_count   
FROM    sys.dm_db_partition_stats ps
INNER JOIN sys.indexes i
    ON  ps.object_id = i.object_id
    AND ps.index_id = i.index_id
INNER JOIN sys.data_spaces ds
    ON  i.data_space_id = ds.data_space_id
WHERE   ps.object_id IN (   SELECT  OBJECT_ID( 'Auth.Tenant' )
                            UNION ALL
                            SELECT  OBJECT_ID( 'Auth.TenantData' ) );

DROP TABLE Auth.TenantData;
DROP TABLE Auth.Tenant;
_

これで、テナントの小さめの「ルックアップ」テーブルに適切に一意のインデックスを設定できるようになり、それらのテナント用にパーティション化しようとしているデータは、パーティション化スキーマの恩恵を受けることができます。

編集:コメントに従って、アラインされていないインデックスは必ずしもパーティション化スキーマのすべての利点を失うわけではないことを付け加えます。RIDルックアップは、どのパーティションであるかを確実に知ることができるためです。に。

3
Avarkx