TenantId
列に基づいてデータを論理的に分離するために、パーティション構成を使用しています。 TenantId
は、テナントの分離が必要なすべてのテーブルのパーティション列とクラスタリングインデックスです。 TenantAlias
とTenantName
も保持する[Tenant]
テーブルに、一意の非クラスター化インデックスを作成したいと思います。パーティション主キーとしてパーティション列の一部でない限り、パーティションスキームに一意のインデックスを作成できません。この事実により、パフォーマンスへの影響(特により大きなコンテキスト)について心配するようになりましたが、これは見当違いの考えかもしれません。
潜在的なパフォーマンスへの影響があったとしても、TenantId
、TenantAlias
、および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
オプション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
があります。 。
私にとっての質問は、何をパーティション化しようとしているのか、何をユニークにしようとしているのかということです。
オプション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ルックアップは、どのパーティションであるかを確実に知ることができるためです。に。