行バージョンで並べ替えられたデータのフィルタリング


8

次の構造のSQLデータテーブルがあります。

CREATE TABLE Data(
    Id uniqueidentifier NOT NULL,
    Date datetime NOT NULL,
    Value decimal(20, 10) NULL,
    RV timestamp NOT NULL,
 CONSTRAINT PK_Data PRIMARY KEY CLUSTERED (Id, Date)
)

個別のIDの数は3000から50000の範囲です
。テーブルのサイズは10 億行を超えます。
1つのIDで、テーブルの5%までの数行をカバーできます。

このテーブルで最も実行されるクエリは次のとおりです。

SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate

更新を含め、Idのサブセットでデータの増分検索を実装する必要があります。
次に、呼び出し元が特定の行バージョンを提供し、データのブロックを取得し、返されたデータの最大行バージョン値を後続の呼び出しに使用する要求スキームを使用しました。

私はこの手順を書きました:

CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
CREATE PROCEDURE GetData
    @Ids guid_list_tbltype READONLY,
    @Cursor rowversion,
    @MaxRows int
AS
BEGIN
    SELECT A.* 
    FROM (
        SELECT 
            Data.Id,
            Date,
            Value,
            RV,
            ROW_NUMBER() OVER (ORDER BY RV) AS RN
        FROM Data
             inner join (SELECT Id FROM @Ids) Ids ON Ids.Id = Data.Id
        WHERE RV > @Cursor
    ) A 
    WHERE RN <= @MaxRows
END

どこ@MaxRowsチャンククライアントが自分のデータをお勧めします方法に応じて500,000 2,000,000の間の範囲であろう。


私はさまざまなアプローチを試しました:

  1. (Id、RV)のインデックス作成:
    CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);

インデックスを使用して、クエリが行求めるRV = @CursorそれぞれのId中に@Ids、その後、ソート結果をマージして、次の行を読んで、。
次に、効率は@Cursor価値の相対的な位置に依存します。
データの終わりに近い場合(RV順)、クエリは瞬時に実行され、そうでない場合、クエリは最大で数分かかる場合があります(最後まで実行しないでください)。

このアプローチの問題は@Cursor、データの終わり近くにあり、並べ替えが苦痛でない(クエリが返す行数が未満の場合でも必要ない@MaxRows)か、さらに遅れており、クエリが@MaxRows * LEN(@Ids)行を並べ替える必要があることです。

  1. RVのインデックス作成:
    CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);

クエリはインデックスを使用して行を探し、そこでRV = @Cursorすべての行を読み取り、要求されていないIDを破棄します@MaxRows
効率は、要求されたIDの%(LEN(@Ids) / COUNT(DISTINCT Id))とその分布に依存します。
要求されたId%が多いほど、破棄される行が少なくなり、読み取りがより効率的になり、要求されたId%が少ないほど、破棄される行が多くなり、同じ量の結果の行に対する読み取りが多くなります。

このアプローチの問題は、リクエストされたIDに含まれる要素が数個しかない場合、インデックス全体を読み取って目的の行を取得する必要がある可能性があることです。

  1. フィルターされたインデックスまたはインデックス付きビューの使用
    CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
    WHERE Id IN (/* list of Ids for specific client*/);

または

    CREATE VIEW vDataClient1 WITH SCHEMABINDING
    AS
    SELECT
        Id,
        Date,
        Value,
        RV
    FROM dbo.Data
    WHERE Id IN (/* list of Ids for specific client*/)
    CREATE UNIQUE CLUSTERED INDEX IDX_IDRV ON vDataClient1(Id, Rv);

この方法では、完全に効率的なインデックス作成とクエリ実行プランが可能になりますが、デメリットがあります。1.実際には、動的SQLを実装してインデックスまたはビューを作成し、要求するプロシージャを変更して適切なインデックスまたはビューを使用する必要があります。2.ストレージを含め、既存のクライアントで1つのインデックスまたはビューを維持する必要があります。3.クライアントがリクエストされたIDのリストを変更する必要があるたびに、インデックスまたはビューを削除して再作成する必要があります。


自分のニーズに合った方法が見つからないようです。
増分データ検索を実装するためのより良いアイデアを探しています。これらのアイデアは、要求しているスキーマまたはデータベーススキーマを作り直すことを意味する可能性があります。


stackoverflow.com/questions/11586004/…のクロスポスト。現時点では、ORA_ROWSCNにインデックスを付けることができない(そして、インデックス付きのマテリアライズドビューではほとんどできない)ことを発見したので、Oracleバージョンを削除しました。
Paciv

日付フィールドはどのように適合しますか?特定のIDと日付の行をテーブルで更新できますか?その場合、日付も更新されますか(追加のタイムスタンプのように?)
8kb

GetData()の試行のように、order byにはId(order by RV、Id)を含める必要があります。(Rv、Id)のインデックスの使用についてコメントできますか?また、前の呼び出しの ">" max rowversionを使用すると、行の行バージョンが同じである場合、チャンク間のレコードが失われるように見えます(それは可能ではありませんか?)。
crokusek 2012

@ 8kb:テーブルで実行される更新ステートメントは、Value列のみを変更します。@crokusek:RVではなくIDで並べ替えるのではなく、RVの代わりに並べ替えのワークロードを増やすだけで、メリットはありません。コメントの理由はわかりません。私が読んだことから、RVは、その列にデータを具体的に挿入しない限り一意である必要がありますが、アプリケーションはそうではありません。
Paciv 2012

クライアントは(Id、Rv)順序で結果を受け入れ、LastRowVersion引数に加えてLastId引数を提供して、ID間のRVソートを排除できますか?私の以前のコメントはすべて、RVが重複しているという仮定に基づいていました。クライアントごとのフィルターされたインデックスは興味深いように見えました。
crokusek 2012

回答:


5

1つの解決策は、クライアントアプリケーションがrowversionIDごとの最大値を記憶することです。ユーザー定義のテーブルタイプは次のように変わります。

CREATE TYPE
    dbo.guid_list_tbltype
AS TABLE 
    (
    Id      uniqueidentifier PRIMARY KEY, 
    LastRV  rowversion NOT NULL
    );

プロシージャ内のクエリは、APPLYパターンを使用するように書き換えることができます(SQLServerCentralの記事のパート1パート2-無料のログインが必要です)。ここでの良好なパフォーマンスの鍵は、ネストされたループ結合での順序のないプリフェッチORDER BYを回避することです。これは、オプティマイザがコンパイル時にテーブル変数のカーディナリティを確認できるようにするために必要です(おそらく望ましい並列プランが作成されます)。RECOMPILE

ALTER PROCEDURE dbo.GetData

    @IDs        guid_list_tbltype READONLY,
    @MaxRows    bigint

AS
BEGIN

    SELECT TOP (@MaxRows)
        d.Id,
        d.[Date],
        d.Value,
        d.RV
    FROM @Ids AS i
    CROSS APPLY
    (
        SELECT
            d.*
        FROM dbo.Data AS d
        WHERE
            d.Id = i.Id
            AND d.RV > i.LastRV
    ) AS d
    ORDER BY
        i.Id,
        d.RV
    OPTION (RECOMPILE);

END;

次のような実行後クエリプランを取得する必要があります(推定プランはシリアルになります)。

クエリプラン


設計変更ソリューションの1つは、クライアントにMAX(RV)IDごと(または内部アプリケーションがすべてのId / RVペアを記憶するサブスクリプションシステム)を記憶させることで、このパターンを他のクライアントに使用します。他の解決策は、クライアントに常にすべてのIDを取得するように強制することでした(これにより、インデックス作成の問題は簡単になります)。それでも、特定のニーズの問題はカバーされていません。クライアントによって提供されるグローバルカウンターが1つだけの、IDのサブセットの増分検索。
Paciv

2

可能であれば、テーブルを再設計します。ギャップのない増分整数としてVersionNumberを使用できる場合、次のチャンクを取得するタスクは完全に簡単な範囲スキャンです。必要なのは次のインデックスだけです。

CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, VersionNumber) INCLUDE(Date, Value);

もちろん、VersionNumberが1で始まり、ギャップがないことを確認する必要があります。これは制約で簡単に実行できます。


グローバルVersionNumberですか、ローカルID ですか?どちらの場合も、それが質問にどのように役立つかわかりません。詳しく説明していただけませんか。
Paciv

0

私がしたこと:

この場合、PKは自動インクリメントされる「代理キー」識別フィールドである必要があります。
すでに数十億に達しているので、BigIntを使用するのが最善です。
それをDataIDと呼びましょう。
この意志:

  • クラスタ化インデックスのすべてのレコードに8バイトを追加します。
  • すべての非クラスター化インデックスのすべてのレコードで16バイトを節約します。
  • あなたが持っていたのは「自然キー」でした:DateTime(8バイト)付きのUniqueIdentifyer(16バイト)。
  • これは、クラスター化インデックスを参照するために、すべてのインデックスレコードの24バイトです。
  • これが、小さい増分整数として代理キーがある理由です。


Clustered-Indexを使用する ように新しいBigInt PK(DataID)を設定します。
これにより:

  • 最後に作成されたレコードが末尾近くに配置されていることを確認してください。
  • 他の非クラスター化インデックスを使用した高速なインデックス作成を可能にします。
  • 他のテーブルへのFKとして将来の拡張を可能にします。


(Date、Id)の周りに非クラスター化インデックスを作成します。
この意志:

  • 最も一般的に使用されるクエリを高速化します。
  • 「値」を追加することもできますが、インデックスのサイズが大きくなり、遅くなります。
  • インデックスの内外で試して、パフォーマンスに大きな違いがあるかどうかを確認することをお勧めします。
  • 追加する場合は、「含める」を使用しないことをお勧めします。
  • (Date、Id、Value)のように追加するだけですが、テストでパフォーマンスが向上することがわかった場合のみです。


(RV、ID)に非クラスター化インデックスを作成します。
この意志:

  • インデックスは常にできるだけ小さくしてください。
  • インデックスに日付と値を含めることで、非常に大きなパフォーマンスの向上に気が付かない限り、ディスクスペースを節約するために、それらを省略することをお勧めします。最初にそれらなしで試してみてください。
  • 日付または値を追加する場合は、「含める」を使用せず、代わりにそれらをインデックスの順序に追加してください。
  • クラスター化されたPKへの新しい挿入でのDataIDの増加のおかげで、最近のRVは通常、最後の方に表示されます(過去のデータを常に更新している場合を除きます)。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.