同じ基本構造の非常に大きなテーブルがいくつかあります。それぞれにRowNumber (bigint)
およびDataDate (date)
列があります。データはSQLBulkImportを使用して毎晩読み込まれ、「新しい」データは読み込まれません。これは履歴レコードです(エンタープライズではなくSQL標準なので、パーティション化は行われません)。
データの各ビットを他のシステムに結び付ける必要があるため、各RowNumber/DataDate
組み合わせは一意です。つまり、私の主キーです。
SSMS Table DesignerでPKを定義した方法が原因で、RowNumber
が最初に、DataDate
が2番目にリストされていることに気づきました。
また、私の断片化は常に非常に高く、最大99%です。
さて、各DataDate
は一度だけ表示されるので、インデクサーは毎日ページに追加されるだけだと思いますが、実際には最初にRowNumber
に基づいてインデックスを作成しているため、他のすべてをシフトしますか?
Rownumber
はID列ではなく、外部システムによって(悲しいことに)生成されたintです。各DataDate
の開始時にリセットされます。
データの例
RowNumber | DataDate | a | b | c.....
1 |2013-08-01| x | y | z
2 |2013-08-01| x | y | z
...
1 |2013-08-02| x | y | z
2 |2013-08-02| x | y | z
...
データはRowNumber
の順序でロードされています。ロードごとに1つのDataDate
です。
インポートプロセスはbcpです。一時テーブルにロードしてから、そこから順番に選択してみました(ORDER BY RowNumber, DataDate
)ですが、依然として高い断片化が発生しています。
PKインデックスの列の順序は重要ですか?
はい、そうです。
既定では、主キー制約は一意のクラスター化インデックスによってSQL Serverで実施されます。クラスタ化インデックスは、テーブル内の行の論理的順序を定義します。 Bツリーインデックスの上位レベルを表すために追加のインデックスページがいくつか追加される場合がありますが、クラスター化インデックスの最低(リーフ)レベルは、単にデータ自体の論理的な順序です。
それを明確にするために、ページ上の行は必ずしも物理的にクラスター化インデックスキーの順序で格納されていません。ページ内には、各行へのポインタを格納する個別の間接構造があります。この構造は、クラスター化インデックスキーで並べ替えられます。また、各ページには、クラスター化インデックスキーの順序で同じレベルの前のページと次のページへのポインターがあります。
クラスター化された主キーが(RowNumber, DataDate)
の場合、行は最初にRowNumber
によって論理的にソートされ、次にDataDate
によってソートされます。したがって、RowNumber = 1
のすべての行が論理的にグループ化され、次にRowNumber = 2
などの行。
新しいデータを追加すると(RowNumbers
が1からnまで)、新しい行は既存のページ内に論理的に属します。そのため、SQL Serverは、ページを分割するために多くの作業を行わなければなりません。このすべてのアクティビティは、多くの追加作業(変更のログ記録を含む)を生み出し、利益がありません。
分割ページも約50%の空から開始されるため、過度の分割によりページ密度が低くなる可能性があります(ページあたりの行数が最適よりも少なくなります)。このディスクからの読み取りの悪いニュース(密度が低い=読み取るページが多い)だけでなく、密度の低いページは、キャッシュされたときにメモリ内の領域をより多く占有します。
クラスタ化インデックスを(DataDate, RowNumber
)に変更すると、新しいデータ(おそらく現在格納されているよりもDataDates
が高い)が新しいページのクラスタ化インデックスの論理的な末尾に追加されます。これにより、ページ分割の不要なオーバーヘッドがなくなり、読み込み時間が短縮されます。データの断片化が少ないことは、先読みアクティビティ(進行中のクエリに必要になる直前にディスクからページを読み取る)の方が効率的であることも意味します。
他に何もない場合、クエリはDataDate
よりもRowNumber
を検索する可能性がはるかに高くなります。 (DataDate, RowNumber
)のクラスター化インデックスは、DataDate
(次にRowNumber
)のインデックスシークをサポートします。既存のアレンジメントは、RowNumber
でのシークのみをサポートします(そのときのみ、DataDate
でのシークのみ)。主キーが変更されると、DataDate
の既存の非クラスター化インデックスを削除できる場合があります。クラスタ化インデックスは、置換される非クラスタ化インデックスよりも幅が広いため、パフォーマンスが許容範囲内であることを確認するためにテストする必要があります。
bcp
を使用して新しいデータをインポートする場合、インポートファイル内のデータがクラスター化インデックスキー(理想的には(DataDate, RowNumber
))でソートされ、bcp
を指定すると、パフォーマンスが向上する可能性があります。オプション:
-h "ORDER(DataDate,RowNumber), TABLOCK"
最高のデータ読み込みパフォーマンスを得るには、最小限のログに記録された挿入を実現しようとする場合があります。詳細については、以下を参照してください。
はい、注文は重要です。 RowNumber(例:WHERE RowNumber=1
)。圧倒的に時系列は日付(WHERE DataDate BEWEEN @start AND @end
)そしてそのようなクエリはDataDate
によるクラスター化された組織を必要とします。
断片化は、一般的には赤字です。ここでは断片化を減らすことは目標ではありませんが、クエリを適切に編成する必要があります。さらに断片化を減らすことは良い考えですが、それだけでは目標ではありません。ワークロードに一致する適切に編成されたデータモデルがある場合(クエリは適切にカバーされます)andパフォーマンスに影響を与える断片化を示す測定値がある場合は、それについて説明できます。