古い値でのテンポラルテーブルのパフォーマンスが低い


8

テンポラルテーブル内の履歴レコードにアクセスすると、奇妙な問題が発生します。AS OF副次句を介してテンポラルテーブルの古いエントリにアクセスするクエリは、最近の履歴エントリのクエリよりも時間がかかります。

履歴テーブルはSQL Serverによって生成され(日付列にクラスター化インデックスが含まれ、ページ圧縮を使用)、履歴テーブルに5,000万行を追加しました。クエリは約25,000行を取得しました。

問題の根本的な原因を特定しようとしましたが、特定できませんでした。これまでにテストしました:

  • クラスター化インデックスを含む5,000万行のテストテーブルを作成して、速度の低下が単にボリュームによるものかどうかを確認します。一定の時間(約400ミリ秒)で25K行を取得できました。
  • 履歴テーブルからページ圧縮を削除します。これは検索時間には影響しませんでしたが、テーブルのサイズを大幅に増やしました。
  • ID列と日付列を使用して、履歴テーブルの行に直接アクセスしてみました。ここが少し面白かった場所です。AS OFサブ句の場合と同様に、約1200ミリ秒かかるテーブルの約400ミリ秒で、古い行にアクセスできました。テストテーブルで日付列のフィルタリングを試みたところ、ID列でのフィルタリングと比較して、同様の速度低下に気づきました。これは、日付の比較がいくつかの減速の背後にあると私に信じさせます。

私はこれをもっと見たいのですが、間違った木を吠えないようにしたいのです。まず、テンポラルテーブルの古い履歴データにアクセスするときに、他の誰かがこれと同じ動作を経験しましたか?次に、パフォーマンスの問題の根本原因をさらに特定するために使用できるいくつかの戦略は何ですか(実行プランを調べ始めたばかりですが、それでも私には少し謎めいています)。

実行計画

これらは単純な取得クエリです。最初のクエリは古い行にアクセスし、2番目のクエリは新しい行にアクセスします。

古い行:実行時間〜1200ms

最近の行〜350msの実行時間

テーブルの詳細

これらはテンポラルテーブルの列です。履歴テーブルには同じ列がありますが、(履歴テーブルの要件に従って)主キーはありません。 テンポラルテーブルの列

以下は、履歴テーブルのインデックスです。 履歴テーブルのインデックス

回答:


6

質問に対するZaneのコメントで、彼は次のように述べています。

...問題の一部のように思えますが、プランで20Kを返すために5000万行を読み取っています。

これが実際に問題です。述部の一部またはすべてをストレージエンジンにプッシュするために使用できるインデックスがありません。マイクロソフトは、ドキュメントの記事「テンポラルテーブルの考慮事項と制限事項」にあるテンポラルテーブルのこのベースラインインデックス作成戦略を推奨しています。

最適なインデックス作成戦略には、現在のテーブルのクラスター化列ストアインデックスまたはBツリー行ストアインデックスと、最適なストレージサイズとパフォーマンスのための履歴テーブルのクラスター化列ストアインデックスが含まれます。独自の履歴テーブルを作成/使用する場合は、期間の終了列から始まる期間列で構成されるこのタイプのインデックスを作成して、一時的なクエリを高速化し、データの一貫性の一部であるクエリを高速化することを強くお勧めします小切手。デフォルトの履歴テーブルには、期間列(終了、開始)に基づいてクラスター化された行ストアインデックスが作成されます。少なくとも、非クラスター化行ストアインデックスをお勧めします

その言い回しは少し混乱します(とにかく私には)。ただし、重要な点は、これらのインデックスを作成してパフォーマンスを向上させることができるということです。

現在のテーブルのNCインデックスSysEndTime

CREATE NONCLUSTERED INDEX IX_SysEndTime_SysStartTime 
ON dbo.Benefits (SysEndTime, SysStartTime)
/*INCLUDE (ideally, include your other important fields here)*/;

これにより、適切な終了時刻を探すことにより、現在のテーブルの一部の行を読み取ることを回避できます。

履歴テーブルのCCI

CREATE CLUSTERED COLUMNSTORE INDEX ix_BenefitsHistory
ON dbo.BenefitsHistory
WITH (DROP_EXISTING = ON);

これにより、履歴テーブルでバッチモードを使用できるようになり、スキャンがはるかに高速になります。

現在のテーブルのNCインデックスSysStartTime

日付範囲クエリのインデックス作成が難しい理由の詳細については、質問の日付範囲を取得する最も効率的な方法に対するPaulの回答を参照してください。そこでのロジックに基づいて、SysStartTimeで先行する現在のテーブルに別のNCインデックスを追加して、オプティマイザが統計とクエリの特定のパラメータに基づいて使用するNCインデックスを選択できるようにするのは理にかなっています。

CREATE NONCLUSTERED INDEX IX_SysStartTime_SysEndTime
ON dbo.Benefits (SysStartTime, SysEndTime)
/*INCLUDE (ideally, include your other important fields here)*/;

上記の3つのインデックスを作成すると、テストケースでのリソース使用量に大きな違いが生じました。合計150万行を返す2つのクエリを実行するテストケースを設定しました。履歴と現在のテーブルの両方に5,000万行あります)。

注:SSMSのオーバーヘッドを減らすために、「実行後に結果を破棄する」オプションを有効にしてテストを実行しました。

実行計画-デフォルトのインデックス

論理読み取り:1,330,612
CPU時間:00:00:14.718
経過時間:00:00:06.198

実行計画-上記のインデックスを使用

論理読み取り:27,656(8,111行ストア+ 19,545列ストア)
CPU時間:00:00:01.828
経過時間:00:00:01.150

ご覧のとおり、合計経過時間を含めて、3秒の測定すべてが6秒から1秒に大幅に減少しました。


ドキュメントの記事で提示されている他のオプションは、クラスター化列ストアインデックスを優先して、現在のテーブルの2つのNCインデックスを無視することです。私のテストでは、パフォーマンスは上記のインデックス作成ソリューションと非常に似ていました。


2

このFOR SYSTEM TIME AS OF句は、指定された時間に存在していたデータセットを返そうとします。これは、リクエストのシステム時間に基づいて、更新を内部でロールバックし、削除を「元に戻す」必要があり、挿入を無視する必要があることを意味します。

過去のAS OF時刻が遠いほど、テンポラルテーブルが指定されたシステム時刻に存在したとおりであることを確認するために検証する必要のある作業が増えるため、クエリにかかる時間が長くなります。

データテーブルが単なるログテーブルであり、データに変更が加えられていない場合、ログに記録された日付とインデックスを使用すると、データがより速く、より一貫して返されます。この場合、一時的な機能を使用するかどうかは不要です。ただし、(挿入以外の)行に変更が加えられた場合、テンポラルテーブル機能を使用することが、要求されている正確なデータ(特定の時点で存在していたテーブルの状態)を返す唯一の方法であり、時間クエリの追加オーバーヘッドを受け入れるだけです。

注:「ロールバック」は実際のロールバックではありません。テンポラルテーブルは、現在のテーブルと履歴テーブルの2つのテーブルを使用します。行が変更されると、前のバージョンのコピーが、行が有効であった時間範囲とともに履歴テーブルに挿入されます。2018年10月20日10:20:20.18に行を挿入し、2018年10月25日10:25:20.18に値を更新し、2018年12月1日12:01:20.18に再度更新すると、次のようになります。開始日が12/01/2018 12:01:20.18である現在のテーブルの行の最新バージョン、および有効範囲が10/20から10/25/2018、および10 /の履歴テーブルの2つの行25から2018年12月1日


ご返信ありがとうございます!それは間違いなく直感的に理解できますが、私が読んだドキュメントではそのような動作についての言及は見つかりませんでした(MSのドキュメントのテンポラルテーブルの基本だけを調べました)。動作をもう少し詳しく説明したドキュメントを知っていますか?
Ebrahim Behbahani
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.