列TRACKING_NUMBERにクラスター化インデックスを持つテーブルに行を挿入するSQLステートメントがあります。
例えば。:
INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC)
SELECT TRACKING_NUMBER, COL_B, COL_C
FROM STAGING_TABLE
私の質問は-クラスター化インデックス列のSELECTステートメントでORDER BY句を使用するのに役立ちますか、それともORDER BY句に必要な追加の並べ替えによって得られる利益は無効になりますか?
他の回答がすでに示しているように、SQL Serverは、行がinsert
の前にクラスター化インデックス順にソートされることを明示的に保証する場合としない場合があります。
これは、プラン内のクラスター化インデックス演算子にDMLRequestSort
プロパティが設定されているかどうかに依存します(これは、挿入される推定行数に依存します)。
SQL Serverが何らかの理由でこれを過小評価していることがわかった場合は、明示的なORDER BY
をSELECT
クエリに追加して、ページ分割を最小限に抑え、INSERT
操作からのフラグメント化を行うことで利益を得ることができます。
例:
use tempdb;
GO
CREATE TABLE T(N INT PRIMARY KEY,Filler char(2000))
CREATE TABLE T2(N INT PRIMARY KEY,Filler char(2000))
GO
DECLARE @T TABLE (U UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),N int)
INSERT INTO @T(N)
SELECT number
FROM master..spt_values
WHERE type = 'P' AND number BETWEEN 0 AND 499
/*Estimated row count wrong as inserting from table variable*/
INSERT INTO T(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
/*Same operation using explicit sort*/
INSERT INTO T2(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
ORDER BY T1.N*1000 + T2.N
SELECT avg_fragmentation_in_percent,
fragment_count,
page_count,
avg_page_space_used_in_percent,
record_count
FROM sys.dm_db_index_physical_stats(2, OBJECT_ID('T'), NULL, NULL, 'DETAILED')
;
SELECT avg_fragmentation_in_percent,
fragment_count,
page_count,
avg_page_space_used_in_percent,
record_count
FROM sys.dm_db_index_physical_stats(2, OBJECT_ID('T2'), NULL, NULL, 'DETAILED')
;
T
が大幅に断片化されていることを示します
avg_fragmentation_in_percent fragment_count page_count avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
99.3116118225536 92535 92535 67.1668272794663 250000
99.5 200 200 74.2868173956017 92535
0 1 1 32.0978502594514 200
しかしT2
の場合、断片化は最小限です
avg_fragmentation_in_percent fragment_count page_count avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
0.376 262 62500 99.456387447492 250000
2.1551724137931 232 232 43.2438349394613 62500
0 1 1 37.2374598468001 232
逆に、データが既に事前に並べ替えられていることがわかっていて、不要な並べ替えを避けたい場合は、SQL Serverに行数を過小評価させることが必要になる場合があります。注目すべき例の1つは、newsequentialid
クラスター化インデックスキーを持つテーブルに多数の行を挿入する場合です。 Denaliより前のバージョンのSQL Serverでは、SQL Serverは不要でコストがかかる可能性のあるソート操作を追加します 。これは回避することができます
DECLARE @var INT =2147483647
INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar
SQL Serverは、Bar
のサイズに関係なく100行が挿入されると推定します。これは、並べ替えがプランに追加されるしきい値を下回っています。ただし、以下のコメントで指摘されているように、これは残念ながら挿入が最小限のロギングを利用できないことを意味します。
オプティマイザが挿入前にデータをソートする方が効率的であると判断した場合、挿入演算子の上流のどこかでソートされます。クエリの一部として並べ替えを導入する場合、オプティマイザはデータが既に並べ替えられていることを認識し、並べ替えを省略する必要があります。選択した実行プランは、ステージングテーブルから挿入された行数によって、実行ごとに異なる場合があることに注意してください。
明示的な並べ替えの有無にかかわらず、プロセスの実行プランをキャプチャできる場合は、コメントに質問に添付してください。
編集:2011-10-28 17:00
@ Gonsaluの答え は、ソート操作が常に発生することを示しているように見えますが、そうではありません。デモスクリプトが必要です!
スクリプトが非常に大きくなるので、それらを Gist に移動しました。実験を簡単にするために、スクリプトはSQLCMDモードを使用します。テストは、2K5SP3、デュアルコア、8GBで実行されます。
挿入テストは3つのシナリオをカバーします。
最初の実行、25行の挿入。
3つの実行プランはすべて同じで、プランのどこにもソートは発生せず、クラスター化インデックススキャンは "ordered = false"です。
2回目の実行、26行の挿入。
今回は計画が異なります。
したがって、オプティマイザがソートが必要であると判断する転換点があります。 @MartinSmithが示すように、これは挿入される推定行に基づいているようです。私のテストリグでは25はソートを必要としません、26はソートを必要としません(2K5SP3、デュアルコア、8GB)
SQLCMDスクリプトには、テーブルの行のサイズを変更できる(ページ密度を変更する)変数と、追加の挿入前のdbo.MyTableの行数が含まれています。私のテストから、どちらも転換点に影響を与えません。
読者の傾向が強い場合は、 スクリプトを実行 して、転換点をコメントとして追加してください。それがテストリグやバージョンによって異なるかどうかを知りたいです。
編集:2011-10-28 20:15
同じリグで2K8R2を使用して繰り返しテスト。今回の転換点は251行です。この場合も、ページ密度と既存の行数を変更しても効果はありません。
SELECT
ステートメントのORDER BY
句は冗長です。
挿入される行をソートする必要がある場合はとにかくソートされるため、冗長です。
テストケースを作成しましょう。
CREATE TABLE #Test (
id INTEGER NOT NULL
);
CREATE UNIQUE CLUSTERED INDEX CL_Test_ID ON #Test (id);
CREATE TABLE #Sequence (
number INTEGER NOT NULL
);
INSERT INTO #Sequence
SELECT number FROM master..spt_values WHERE name IS NULL;
実際のクエリプランのテキスト表示を有効にして、クエリプロセッサによって実行されるタスクを確認できるようにします。
SET STATISTICS PROFILE ON;
GO
では、ORDER BY
句なしでINSERT
2K行をテーブルに入れましょう。
INSERT INTO #Test
SELECT number
FROM #Sequence
このクエリの実際の実行プランは次のとおりです。
INSERT INTO #Test SELECT number FROM #Sequence
|--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
|--Top(ROWCOUNT est 0)
|--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
|--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))
ご覧のとおり、実際のINSERTが発生する前にソート演算子があります。
次に、テーブルをクリアして、ORDER BY
句を使用してINSERT
2k行をテーブルに追加します。
TRUNCATE TABLE #Test;
GO
INSERT INTO #Test
SELECT number
FROM #Sequence
ORDER BY number
このクエリの実際の実行プランは次のとおりです。
INSERT INTO #Test SELECT number FROM #Sequence ORDER BY number
|--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
|--Top(ROWCOUNT est 0)
|--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
|--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))
これは、ORDER BY
句なしでINSERT
ステートメントに使用されたものと同じ実行プランであることに注意してください。
現在、Sort
操作は必ずしも必要ではありません Mark Smithが別の回答で示したように (挿入される行の数が少ない場合)、しかしORDER BY
明示的なORDER BY
を使用しても、クエリプロセッサによってSort
操作が生成されないため、この場合も句は冗長です。
最小限のログに記録されたINSERT
を使用して、INSERT
ステートメントをクラスター化インデックス付きのテーブルに最適化できますが、これはこの質問の範囲外です。
2011年11月2日更新:Mark Smithが示したように 、INSERT
sをクラスター化されたテーブルにインデックスは常にソートする必要があるとは限りません-ただし、その場合、ORDER BY
句も冗長になります。