web-dev-qa-db-ja.com

クラスター化インデックスを含むテーブルへの効率的なINSERT INTO

列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句に必要な追加の並べ替えによって得られる利益は無効になりますか?

29
GWR

他の回答がすでに示しているように、SQL Serverは、行がinsertの前にクラスター化インデックス順にソートされることを明示的に保証する場合としない場合があります。

これは、プラン内のクラスター化インデックス演算子にDMLRequestSortプロパティが設定されているかどうかに依存します(これは、挿入される推定行数に依存します)。

SQL Serverが何らかの理由でこれを過小評価していることがわかった場合は、明示的なORDER BYSELECTクエリに追加して、ページ分割を最小限に抑え、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行が挿入されると推定します。これは、並べ替えがプランに追加されるしきい値を下回っています。ただし、以下のコメントで指摘されているように、これは残念ながら挿入が最小限のロギングを利用できないことを意味します。

18
Martin Smith

オプティマイザが挿入前にデータをソートする方が効率的であると判断した場合、挿入演算子の上流のどこかでソートされます。クエリの一部として並べ替えを導入する場合、オプティマイザはデータが既に並べ替えられていることを認識し、並べ替えを省略する必要があります。選択した実行プランは、ステージングテーブルから挿入された行数によって、実行ごとに異なる場合があることに注意してください。

明示的な並べ替えの有無にかかわらず、プロセスの実行プランをキャプチャできる場合は、コメントに質問に添付してください。

編集:2011-10-28 17:00

@ Gonsaluの答え は、ソート操作が常に発生することを示しているように見えますが、そうではありません。デモスクリプトが必要です!

スクリプトが非常に大きくなるので、それらを Gist に移動しました。実験を簡単にするために、スクリプトはSQLCMDモードを使用します。テストは、2K5SP3、デュアルコア、8GBで実行されます。

挿入テストは3つのシナリオをカバーします。

  1. ターゲットと同じ順序でデータのクラスター化インデックスをステージングします。
  2. データクラスター化インデックスを逆の順序でステージングします。
  3. ランダムなINTを含むcol2によってクラスター化されたステージングデータ。

最初の実行、25行の挿入。

1st run, 25 rows

3つの実行プランはすべて同じで、プランのどこにもソートは発生せず、クラスター化インデックススキャンは "ordered = false"です。

2回目の実行、26行の挿入。

2nd run, 26 rows

今回は計画が異なります。

  • 最初は、クラスター化インデックススキャンをordered = falseとして示しています。ソースデータが適切に並べ替えられているため、並べ替えは行われていません。
  • 2番目では、クラスター化インデックススキャンは、ordered = trueを逆方向にスキャンします。したがって、ソート操作はありませんが、データをソートする必要性はオプティマイザによって認識され、逆の順序でスキャンされます。
  • 3番目は、ソート演算子を示しています。

したがって、オプティマイザがソートが必要であると判断する転換点があります。 @MartinSmithが示すように、これは挿入される推定行に基づいているようです。私のテストリグでは25はソートを必要としません、26はソートを必要としません(2K5SP3、デュアルコア、8GB)

SQLCMDスクリプトには、テーブルの行のサイズを変更できる(ページ密度を変更する)変数と、追加の挿入前のdbo.MyTableの行数が含まれています。私のテストから、どちらも転換点に影響を与えません。

読者の傾向が強い場合は、 スクリプトを実行 して、転換点をコメントとして追加してください。それがテストリグやバージョンによって異なるかどうかを知りたいです。

編集:2011-10-28 20:15

同じリグで2K8R2を使用して繰り返しテスト。今回の転換点は251行です。この場合も、ページ密度と既存の行数を変更しても効果はありません。

12

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が示したようにINSERTsをクラスター化されたテーブルにインデックスは常にソートする必要があるとは限りません-ただし、その場合、ORDER BY句も冗長になります。

8
gonsalu