連続処理中のインデックスの断片化


10

SQL Server 2005

900Mのレコードテーブルで約350Mのレコードを継続的に処理できる必要があります。処理するレコードを選択するために使用しているクエリは、処理中に著しく断片化され、インデックスを再構築するために処理を停止する必要があります。疑似データモデルとクエリ...

/**************************************/
CREATE TABLE [Table] 
(
    [PrimaryKeyId] [INT] IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    [ForeignKeyId] [INT] NOT NULL,
    /* more columns ... */
    [DataType] [CHAR](1) NOT NULL,
    [DataStatus] [DATETIME] NULL,
    [ProcessDate] [DATETIME] NOT NULL,
    [ProcessThreadId] VARCHAR (100) NULL
);

CREATE NONCLUSTERED INDEX [Idx] ON [Table] 
(
    [DataType],
    [DataStatus],
    [ProcessDate],
    [ProcessThreadId]
);
/**************************************/

/**************************************/
WITH cte AS (
    SELECT TOP (@BatchSize) [PrimaryKeyId], [ProcessThreadId]
    FROM [Table] WITH ( ROWLOCK, UPDLOCK, READPAST )
    WHERE [DataType] = 'X'
    AND [DataStatus] IS NULL
    AND [ProcessDate] < DATEADD(m, -2, GETDATE()) -- older than 2 months
    AND [ProcessThreadId] IS NULL
)
UPDATE cte
SET [ProcessThreadId] = @ProcessThreadId;

SELECT * FROM [Table] WITH ( NOLOCK )
WHERE [ProcessThreadId] = @ProcessThreadId;
/**************************************/

データの内容...
列[DataType]はCHAR(1)として入力されますが、すべてのレコードの約35%は「X」に等しく、残りは「A」に等しくなります。
[DataType]が 'X'に等しいレコードのみのうち、約10%がNOT NULL [DataStatus]値を持ちます。

[ProcessDate]列と[ProcessThreadId]列は、処理されるすべてのレコードに対して更新されます。
[DataType]列が約10%更新されます(「X」が「A」に変更されます)。
[DataStatus]列が更新される時間は1%未満です。

今のところ私の解決策は、すべてのレコードの主キーを選択して、別の処理テーブルに処理することです。インデックスフラグメントとして処理するレコードが少なくなるように、キーを処理するときに削除します。

ただし、これは、手動での介入や大幅なダウンタイムなしでこれらのデータが継続的に処理されるようにしたいワークフローには適合しません。ハウスキーピングの家事のために、四半期ごとにダウンタイムを予想しています。しかし今は、個別の処理テーブルがないと、断片化が悪化してインデックスを停止して再構築する必要が生じない限り、データセットの半分でも処理できません。

インデックス付けまたは別のデータモデルに関する推奨事項はありますか?調査する必要があるパターンはありますか?
私はデータモデルとプロセスソフトウェアを完全に制御できるので、何も問題はありません。


考えも1つ:インデックスの順序が間違っているようです。では、ProcessThreadId、ProcessDate、DataStatus、DataTypeなどでしょうか。
GBN 2012

チャットで宣伝しました。非常に良い質問です。chat.stackexchange.com/rooms/179/the-heap
gbn

選択をより正確に表すためにクエリを更新しました。これを実行している複数の同時スレッド。選択的な注文の推奨事項に注意しました。ありがとう。
Chris Gallucci

@ChrisGallucciできればチャットに来てください...
JNK

回答:


4

あなたがやっていることは、テーブルをキューとして使用していることです。更新はデキューメソッドです。ただし、テーブルのクラスター化インデックスは、キューには適していません。キューとしてテーブルを使用すると、実際にはテーブルの設計に非常に厳しい要件が課されます。クラスタ化されたインデックス、デキューの順序でなければなりません([DataType], [DataStatus], [ProcessDate])。この場合はです。主キーを非クラスター化制約として実装できます。Idxクラスター化されたキーが役割を果たすため、非クラスター化インデックスを削除します。

パズルのもう1つの重要な部分は、処理中に行サイズを一定に保つことです。フィールド値がNULLから非NULLに変更されるため、行が「処理」されているように拡大および縮小することを意味するとしてを宣言しProcessThreadIdましたVARCHAR(100)。行のこの拡大縮小パターンにより、ページ分割と断片化が発生します。'VARCHAR(100)'のスレッドIDは想像できません。おそらく固定長の型を使用してくださいINT

補足として、2つのステップ(UPDATEの後にSELECT)でデキューする必要はありません。上記のリンク先の記事で説明されているように、OUTPUT句を使用できます。

/**************************************/
CREATE TABLE [Table] 
(
    [PrimaryKeyId] [INT] IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
    [ForeignKeyId] [INT] NOT NULL,
    /* more columns ... */
    [DataType] [CHAR](1) NOT NULL,
    [DataStatus] [DATETIME] NULL,
    [ProcessDate] [DATETIME] NOT NULL,
    [ProcessThreadId] INT NULL
);

CREATE CLUSTERED INDEX [Cdx] ON [Table] 
(
    [DataType],
    [DataStatus],
    [ProcessDate]
);
/**************************************/

declare @BatchSize int, @ProcessThreadId int;

/**************************************/
WITH cte AS (
    SELECT TOP (@BatchSize) [PrimaryKeyId], [ProcessThreadId] , ... more columns 
    FROM [Table] WITH ( ROWLOCK, UPDLOCK, READPAST )
    WHERE [DataType] = 'X'
    AND [DataStatus] IS NULL
    AND [ProcessDate] < DATEADD(m, -2, GETDATE()) -- older than 2 months
    AND [ProcessThreadId] IS NULL
)
UPDATE cte
SET [ProcessThreadId] = @ProcessThreadId
OUTPUT DELETED.[PrimaryKeyId] , ... more columns ;
/**************************************/

さらに、正常に処理されたアイテムを別のアーカイブテーブルに移動することを検討します。キューテーブルのサイズをゼロ近くに移動させたい場合、不要な古いエントリの「履歴」を保持しているため、キューテーブルが大きくならないようにします。また[ProcessDate]、別の方法でパーティション化を検討することもできます(つまり、キューとして機能し、NULL ProcessDateのエントリを格納する現在のアクティブなパーティションと、null以外のすべてのパーティションを別のパーティションに分割します。必須の保存期間を過ぎたデータを削除(スイッチアウト)します。状況が熱くなった場合は、[DataType] 十分な選択性がありますが、永続的な計算列([DataType]と[ProcessingDate]を結合する複合列)によるパーティション分割が必要なため、その設計は本当に複雑になります。


3

ProcessDateProcessthreadidフィールドを別のテーブルに移動することから始めます。

現在、このかなり広いインデックスから選択するすべての行も更新する必要があります。

これら2つのフィールドを別のテーブルに移動すると、メインテーブルの更新量が90%削減され、ほとんどの断片化が処理されます。

NEWテーブルには依然としてフラグメント化がありますが、データがはるかに少ない狭いテーブルで管理する方が簡単です。


これと、[DataType]に基づいてデータを物理的に分割すると、必要な場所に移動できます。私は現在、この設計(実際には再設計)フェーズにいるので、この変更をテストする機会が得られるまでには時間がかかります。
クリスギャルッチ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.