テーブルが分割されているときのテーブルの物理的なレイアウトについていくつか質問があります。私はこれを研究してきましたが、まだ少しわかりません。
既存のテーブルがあるとしましょう:-
CREATE TABLE dbo.[ExampleTable]
(ID INT IDENTITY(1,1),
Col1 SYSNAME,
Col2 SYSNAME,
CreatedDATE DATE) ON [DATA];
ALTER TABLE dbo.[ExampleData] ADD CONSTRAINT [PK_ExampleTable] PRIMARY KEY CLUSTERED
( [ID] ASC )
GO
このテーブルをCreatedDate列(この例では同じファイルグループ内のすべてのパーティション)でパーティション分割したいのですが、列をそれ自体で主キーとして使用することはできません。したがって、CreatedDate列を主キーに追加します。
ALTER TABLE dbo.[ExampleTable] DROP CONSTRAINT PRIMARY KEY
ALTER TABLE dbo.[ExampleTable] ADD CONSTRAINT [PK_ExampleTable] PRIMARY KEY CLUSTERED
( [ID] ASC, [CreatedDate] ASC ) ON PartitionScheme(CreatedDate)
GO
私の質問は、データはどのように並べ替えられるのですか?データはCreatedDate列で物理的にパーティションに分割され、ID列で並べ替えられますか?または、パーティションは論理的であり、データはID列順に並べられたままですか?
また、ID列がGUIDの場合はどうなりますか?データはパーティションにあり、それらのパーティション内で恐ろしく断片化されますか?
何かアドバイスをいただければ幸いです。ありがとうございます。
アンドリュー
編集:-パーティションスキームと機能に追加:-
DECLARE @CurrentDate DATETIME;
CREATE PARTITION FUNCTION PF_Example (DATETIME)
AS RANGE RIGHT
FOR VALUES (@CurrentDate+7,@CurrentDate+6,@CurrentDate+5,@CurrentDate+4,
@CurrentDate+3,@CurrentDate+2,@CurrentDate+1,@CurrentDate,
@CurrentDate-1,@CurrentDate-2,@CurrentDate-3,@CurrentDate-4,
@CurrentDate-5,@CurrentDate-6,@CurrentDate-7,@CurrentDate-8);
CREATE PARTITION SCHEME PS_Example
AS PARTITION PF_Example
ALL TO (Data);
わかりましたので、これが理由を示す簡単な例です-ほとんどの操作(レポートクエリ、アーカイブ操作、パーティションスイッチなど)が日付の範囲で行の範囲を識別する場合-パーティション列でのクラスタリングのほうが適しています。簡単な日付ベースのパーティション構成と関数を見てみましょう:
CREATE PARTITION FUNCTION DateRange (DATE)
AS RANGE RIGHT FOR VALUES ('20150101');
GO
CREATE PARTITION SCHEME DateRangeScheme
AS PARTITION DateRange ALL TO ([PRIMARY]);
GO
次に、2つのテーブル-1つはID、日付にクラスター化PKがあり、Dateは非クラスター化インデックス、もう1つはID、日付に非クラスター化PKがあり、日付はクラスター化インデックスです。
CREATE TABLE dbo.PKClustered
(
ID INT,
dt DATE,
filler CHAR(4000)
CONSTRAINT df_filler_c DEFAULT '' NOT NULL,
CONSTRAINT pk_clust PRIMARY KEY CLUSTERED (ID,dt)
);
CREATE INDEX dt ON dbo.PKClustered(dt) ON DateRangeScheme(dt);
CREATE TABLE dbo.PKNonClustered
(
ID INT,
dt DATE,
filler CHAR(4000)
CONSTRAINT df_filler_nc DEFAULT '' NOT NULL,
CONSTRAINT pk_nonclust PRIMARY KEY NONCLUSTERED (ID,dt)
);
CREATE CLUSTERED INDEX dt ON dbo.PKNonClustered(dt) ON DateRangeScheme(dt);
次に、いくつかのデータを入力します。
INSERT dbo.PKClustered(ID, dt) SELECT TOP (100) Number, '20141231'
FROM master.dbo.spt_values WHERE [type] = N'P' ORDER BY Number;
INSERT dbo.PKClustered(ID, dt) SELECT TOP (50) Number, '20150101'
FROM master.dbo.spt_values WHERE [type] = N'P' ORDER BY Number DESC;
INSERT dbo.PKNonClustered(ID, dt) SELECT ID, dt FROM dbo.PKClustered;
したがって、パーティション1に100行、パーティション2に50行必要です。 sys.partitions
確認:
SELECT [table] = o.name, [index] = i.name,
p.partition_number, p.[rows]
FROM sys.tables AS o
INNER JOIN sys.indexes AS i
ON o.[object_id] = i.[object_id]
INNER JOIN sys.partitions AS p
ON i.[object_id] = p.[object_id]
AND i.index_id = p.index_id
WHERE o.name LIKE N'PK%Clustered'
ORDER BY o.name, i.name;
結果:
どちらの場合も、PKのデータはすべて単一のパーティションに格納されていることに注意してください。それはクエリにどのように影響しますか?まあ、おそらくこれらは4つの典型的なものであると考えてください(SELECT *
は別として、簡潔にするためにのみ使用されています)。
SELECT * FROM dbo.PKClustered WHERE dt >= '20150101';
SELECT * FROM dbo.PKNonClustered WHERE dt >= '20150101';
DELETE dbo.PKClustered WHERE dt >= '20140101' AND dt < '20150101';
DELETE dbo.PKNonClustered WHERE dt >= '20140101' AND dt < '20150101';
SQL Sentry Plan Explorer :*の結果をいくつか示します。
推定コストと実際のランタイムメトリック:
非クラスター化PKに対するSELECT *
は、単一のパーティションのみにアクセスして、効率的なクラスター化インデックスシークを実行しました。
PKがクラスター化されている場合は、代わりにクラスター化インデックススキャンを実行することを決定します。つまり、パーティションを排除できないため、読み取りが多くなり、I/Oコストが高くなります。興味深いことに、スキャンは順序付けられていません。
削除でも同様のことが起こります。どちらの場合も、削除操作で最もコストがかかるのは、クラスター化インデックスの削除です。パーティションを削除するという利点があるため、この操作をサポートするには、非クラスター化PKの方がはるかに望ましい(最終的に必要な読み取りとそれ以上の読み取りはほぼ同じであっても)。
クラスター化されたPKを使用すると、ソース行はシーク(より効率的であると期待できる)で検出されますが、ほとんどの作業は後続の削除によって実行されるため、少なくともこのサイズでは、それほど大きな影響はありません。すべて:
さて、より大きなボリュームでは、その主要なスキャンがスケールを逆方向に傾ける可能性があるため、テストする必要があります。
もちろん、このローエンドでは、IDで特定する単一行クエリに悪影響を及ぼします。通常、行はインデックスシークで特定し、ルックアップを実行する必要があるため、単一のクラスター化インデックスシークではありません。次の2つのクエリを考えてみましょう(ここでも、SELECT *
に関しては、私が行うのではなく、私が言うように行います)。
SELECT * FROM dbo.PKClustered WHERE ID = 2045;
SELECT * FROM dbo.PKNonClustered WHERE ID = 2045;
プランエクスプローラーからの結果:
最初のものは単純です。クラスター化インデックスシークが必要なだけです(したがってルックアップはありません)。
しかし、前述のように、2番目はPKに対してパーティション化されていないシークを決定しますが、パーティション化されたキールックアップを決定します。この場合、結果的にコストが高くなりますが、必ずしもそうとは限らず、オプティマイザが常に選択できるとは限りません。
行の数と結合の構成方法によっては、特定の結合クエリでも同じようなことが起こります。
ここでも、ここでのオプティマイザの選択は、多くの場合、ボリュームに依存します。したがって、最後に:それは依存します。あなたが提供した情報で私が選択したのは、区分化キーでクラスター化し、クラスター化されていないPKを使用することです。そして、私はどちらの場合もこのIDにGUIDを使用することを強く避けます-1秒あたり80億行を挿入しようとしている場合、その分布は挿入に適しているかもしれませんが、役に立たないでしょうあなたがしている他の何かのために。
別のオプションは、最初に日付の単一の結合PKを使用し、次にIDを使用することです。
CREATE TABLE dbo.PKCombined
(
ID INT,
dt DATE,
filler CHAR(4000)
CONSTRAINT df_filler_comb DEFAULT '' NOT NULL,
CONSTRAINT pk_comb PRIMARY KEY CLUSTERED (dt,ID) ON DateRangeScheme(dt)
);
これにより、少ないページに格納される行が少なくなります(たとえば、非クラスター化インデックスを維持する必要がなくなります)。
SELECT [table] = o.name,
[rows] = SUM(row_count),
[pages] = SUM(used_page_count),
[size_in_kb] = 8.192*SUM(used_page_count)
FROM sys.tables AS o
INNER JOIN sys.indexes AS i
ON o.[object_id] = i.[object_id]
INNER JOIN sys.dm_db_partition_stats AS p
ON i.[object_id] = p.[object_id]
AND i.index_id = p.index_id
WHERE o.name LIKE N'PK%'
GROUP BY o.name
ORDER BY o.name;
結果:
しかし、それはこれらの他のクエリにどのように影響しますか? SELECT *
は、非クラスター化PKバージョンのSELECT *
と同じです。単純なクラスター化インデックスシーク。ただし、DELETE
ははるかに単純な計画です。
ただし、単一行のシークははるかにより高価になります。
おそらく、IDの非クラスター化カバリングインデックスを使用して、スキャンをシークに変換できます(インデックスが非カバリングの場合はルックアップを使用)が、パーティションの削除によるメリットは得られません。
*
免責事項:私はSQLセントリーで働いています。