web-dev-qa-db-ja.com

300のパーティションを持つパーティションテーブルのインデックスの再構築

シナリオ

パーティションテーブルが空で、180k行の1つのパーティションのデータを読み込んでいます。インデックスを無効にしてデータを読み込み、データが読み込まれた後にインデックスを再構築しました。

問題

再構築されたインデックスのクエリプランを調べると、「推定行数」は180kですが、「実際の行数」は300パーティション* 180,000行= 5400万行ですが、1つのパーティションのみのデータをロードしています。

enter image description hereenter image description here

この振る舞いとこの問題を克服する方法に光を当てることができますか?

Microsoft SQL Server 2016(SP2)(KB4052908)-13.0.5026.0(X64)Mar 18 2018 2018 09:11:49 Copyright(c)Microsoft Corporation Enterprise Edition(64-bit)on Windows Server 2012 R2 Standard 6.3(Build 9600:)

6
Rsola

再構築されたインデックスのクエリプランを調べると、「推定行数」は180kですが、「実際の行数」は300パーティション* 180,000行= 5400万行ですが、1つのパーティションのみのデータをロードしています。

あなたの数学はここから少しずれています。提供された画像は、364回の反復(364 * 1,853,250 = 674,582,272)で推定1,853,250行(180kではない)と合計674,582,272行(5400万ではない)を示しています。

それでも問題は残ります。テーブル全体が1,853,250行しか保持していないのに、SQL Serverがなぜ6億7,400万行を読み取って非クラスター化インデックスを再構築するのでしょうか。

示されている実行プランは、同じ場所にある結合です。次の操作を実行します。

  1. 定数スキャンは、テーブルの各パーティションのパーティション番号を保持します。
  2. 各パーティション:
    1. テーブルを完全にスキャンする(クラスター化インデックススキャン)
    2. 行を非クラスター化インデックス順に並べ替えます(並べ替え)
    3. 現在のパーティションの行をフィルターします(フィルター)
    4. 非クラスター化インデックスに行を挿入する(インデックス挿入)

これは明らかに物事を進めるためのひどく非効率的な方法です。しかし、オプティマイザはおかしくありません。一般的な考え方は健全です。問題は、フィルタの適用が遅すぎることです。通常、「現在のパーティション」述語はクラスター化インデックススキャンにプッシュされ、現在のパーティションを選択するシークとして表示されます。

どのように機能するか

生成される通常の並列プランは次のようになります。

Normal parallel plan

Filter演算子がないことに注意してください。その述語がスキャンにプッシュされました:

Scan with seek

はい、これはシークを伴うスキャンです。アイデアは、ループの各反復で現在のパーティションのみから行をフェッチすることです。そのため、各反復は非クラスター化インデックスの1つのパーティションを構築します。

あなたの場合

floatまたはbigintデータ型をパーティション列として使用して、質問に示された実行プランを再現することができました。フィルターのカーディナリティー推定のいくつかの側面(おそらく推測)により、オプティマイザーは「現在のパーティション」述語をソートを超えてクラスター化インデックススキャンにプッシュできなくなります。

次のデモでは、非クラスター化インデックスが無効になっている300パーティションテーブルを作成し、100万行を単一のパーティションにロードしてから、非クラスター化インデックスを再構築します。 bigintデータ型を使用します。

DROP TABLE IF EXISTS dbo.Test;

IF EXISTS (SELECT * FROM sys.partition_schemes AS PS WHERE PS.[name] = N'PS')
    DROP PARTITION SCHEME PS;

IF EXISTS (SELECT * FROM sys.partition_functions AS PF WHERE PF.[name] = N'PF')
DROP PARTITION FUNCTION PF;
GO
CREATE PARTITION FUNCTION PF (bigint)
AS RANGE RIGHT 
FOR VALUES 
(
    0,
    1000000,2000000,3000000,4000000,5000000,6000000,7000000,8000000,9000000,10000000,
    11000000,12000000,13000000,14000000,15000000,16000000,17000000,18000000,19000000,
    20000000,21000000,22000000,23000000,24000000,25000000,26000000,27000000,28000000,
    29000000,30000000,31000000,32000000,33000000,34000000,35000000,36000000,37000000,
    38000000,39000000,40000000,41000000,42000000,43000000,44000000,45000000,46000000,
    47000000,48000000,49000000,50000000,51000000,52000000,53000000,54000000,55000000,
    56000000,57000000,58000000,59000000,60000000,61000000,62000000,63000000,64000000,
    65000000,66000000,67000000,68000000,69000000,70000000,71000000,72000000,73000000,
    74000000,75000000,76000000,77000000,78000000,79000000,80000000,81000000,82000000,
    83000000,84000000,85000000,86000000,87000000,88000000,89000000,90000000,91000000,
    92000000,93000000,94000000,95000000,96000000,97000000,98000000,99000000,100000000,
    101000000,102000000,103000000,104000000,105000000,106000000,107000000,108000000,109000000,
    110000000,111000000,112000000,113000000,114000000,115000000,116000000,117000000,118000000,119000000,
    120000000,121000000,122000000,123000000,124000000,125000000,126000000,127000000,128000000,129000000,
    130000000,131000000,132000000,133000000,134000000,135000000,136000000,137000000,138000000,139000000,
    140000000,141000000,142000000,143000000,144000000,145000000,146000000,147000000,148000000,149000000,
    150000000,151000000,152000000,153000000,154000000,155000000,156000000,157000000,158000000,159000000,
    160000000,161000000,162000000,163000000,164000000,165000000,166000000,167000000,168000000,169000000,
    170000000,171000000,172000000,173000000,174000000,175000000,176000000,177000000,178000000,179000000,
    180000000,181000000,182000000,183000000,184000000,185000000,186000000,187000000,188000000,189000000,
    190000000,191000000,192000000,193000000,194000000,195000000,196000000,197000000,198000000,199000000,
    200000000,201000000,202000000,203000000,204000000,205000000,206000000,207000000,208000000,209000000,
    210000000,211000000,212000000,213000000,214000000,215000000,216000000,217000000,218000000,219000000,
    220000000,221000000,222000000,223000000,224000000,225000000,226000000,227000000,228000000,229000000,
    230000000,231000000,232000000,233000000,234000000,235000000,236000000,237000000,238000000,239000000,
    240000000,241000000,242000000,243000000,244000000,245000000,246000000,247000000,248000000,249000000,
    250000000,251000000,252000000,253000000,254000000,255000000,256000000,257000000,258000000,259000000,
    260000000,261000000,262000000,263000000,264000000,265000000,266000000,267000000,268000000,269000000,
    270000000,271000000,272000000,273000000,274000000,275000000,276000000,277000000,278000000,279000000,
    280000000,281000000,282000000,283000000,284000000,285000000,286000000,287000000,288000000,289000000,
    290000000,291000000,292000000,293000000,294000000,295000000,296000000,297000000,298000000,299000000,
    300000000
);
CREATE PARTITION SCHEME PS
AS PARTITION PF
ALL TO ([PRIMARY]);
GO
CREATE TABLE dbo.Test
(
    c1 bigint NOT NULL,
    c2 bigint NOT NULL,

    CONSTRAINT [PK dbo.Test (c1)]
    PRIMARY KEY CLUSTERED (c1)
    ON PS (c1),

    INDEX [IX dbo.Test (c2)]
    NONCLUSTERED (c2)
    ON PS (c1)
)
ON PS (c1);
GO
ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test 
DISABLE;
GO
INSERT dbo.Test WITH (TABLOCKX)
(
    c1,
    c2
)
SELECT
    CONVERT(bigint, SV1.number * 1000 + SV2.number),
    CONVERT(bigint, (SV1.number * 1000 + SV2.number) * 2)
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2
WHERE
    SV1.[type] = N'P'
    AND SV2.[type] = N'P'
    AND SV1.number >= 0
    AND SV1.number < 1000
    AND SV2.number >= 0
    AND SV2.number < 1000;
GO
ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test
REBUILD;

インデックスの再構築計画の実行には約90秒かかります。

bigint data type plan

対照的に、パーティショニングカラムがintegerデータ型の場合、再構築には2秒かかります(効率的なscan-with-a -以前に示した計画を求める)。

これは、パーティションIDが本来integerであるため(フィルター述語での変換は不要)、おそらく現時点では詳細は不明です。

回避策

上記は、デフォルト(最新)のカーディナリティ推定モデルを使用した場合にのみ再現されます。インデックスの再構築ステートメントで直接ヒントを指定することはできないため、以下では、文書化されたトレースフラグ9481を使用して、代わりにレガシー(元の)カーディナリティ推定モデルを一時的に使用しています。

ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test 
DISABLE;

DBCC TRACEON (9481);

ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test
REBUILD;

DBCC TRACEOFF (9481);

これにより、最適な実行プランが作成され、約2秒で完了します。

実際の解決策

テーブルの単一のパーティションにデータを追加またはロードするより良い方法があります。アイデアは、既存のパーティションをスタンドアロンテーブルにSWITCHアウトし、そこにデータをロードしてから、パーティションをSWITCH戻すことです。

DROP TABLE IF EXISTS dbo.TestSwitchP2;
GO
-- Table with constraints limiting values to the target partition
CREATE TABLE dbo.TestSwitchP2
(
    c1 float NOT NULL PRIMARY KEY,
    c2 float NOT NULL,

    CHECK (c1 >= 0 AND c1 < 1000000)
)
ON [PRIMARY];
GO
-- Switch existing rows into the working table
ALTER TABLE dbo.Test
SWITCH PARTITION 2
TO dbo.TestSwitchP2;
GO
-- Add new rows
INSERT dbo.TestSwitchP2 WITH (TABLOCKX)
(
    c1,
    c2
)
SELECT
    CONVERT(float, SV1.number * 1000 + SV2.number),
    CONVERT(float, (SV1.number * 1000 + SV2.number) * 2)
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2
WHERE
    SV1.[type] = N'P'
    AND SV2.[type] = N'P'
    AND SV1.number >= 0
    AND SV1.number < 1000
    AND SV2.number >= 0
    AND SV2.number < 1000;
GO
-- Create a compatible nonclustered index
CREATE INDEX i ON dbo.TestSwitchP2 (c2);
GO
-- Switch the partition back in to the main table
ALTER TABLE dbo.TestSwitchP2
SWITCH TO dbo.Test 
PARTITION 2;

パーティションの切り替えはメタデータのみの操作であり、通常は即座に完了します。

上記の実装は、2秒未満で100万の新しい行をロードします。メインテーブルの非クラスター化インデックスを無効にする必要はありません。

14
Paul White 9