回答:
他の回答がすでに示しているように、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行です。繰り返しますが、ページ密度と既存の行数を変更しても効果はありません。
ステートメントORDER BY
内の句SELECT
は冗長です。
ソートされる必要がある場合、挿入される行はとにかくソートされるため、冗長です。
テストケースを作成しましょう。
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
ここでINSERT
、ORDER BY
句なしで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が発生する前にソート演算子があります。
次に、テーブルをクリアINSERT
し、ORDER BY
句を使用して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]))
句のINSERT
ないステートメントに使用されたのと同じ実行プランであることに注意してくださいORDER BY
。
現在、マークスミスが別の回答で示しているように(挿入する行の数が少ない場合)、Sort
操作は必ずしも必要ではありませんが、明示的なであっても操作は生成されないため、その場合は句はまだ冗長ですクエリプロセッサによって。ORDER BY
ORDER BY
Sort
INSERT
minimally-loggedを使用して、クラスター化インデックスを使用してステートメントをテーブルに最適化できますがINSERT
、これはこの質問の範囲外です。
2011年11月2日更新: Mark Smithが示したように、INSERT
クラスター化インデックスのあるテーブルへのsは常にソートする必要があるとは限りませんがORDER BY
、その場合も句は冗長です。