SQL Serverテーブルの変更を検出する


13

私のアプリケーションでは、SQL Server 2012で実行されているDBを使用して、高価なクエリを定期的に実行し、後でアプリケーションがクエリできるテーブルに結果を書き込むジョブ(スケジュールされたタスク)があります。

理想的には、クエリが最後に実行されてから何かが変更された場合にのみ、その高価なクエリを実行したいと思います。ソーステーブルは非常に大きいため、すべての候補列などのチェックサムを選択することはできません。

私は次のアイデアを持っています:

  • ソーステーブルで何かを変更するたびに、最後に変更されたタイムスタンプ、「クエリである必要があります」フラグ、またはこのようなものを追跡テーブルに明示的に書き込みます。
  • トリガーを使用して同じことを行います。

ただし、書き込みを明示的に追跡せずに、テーブルの変更を簡単に検出する方法があるかどうかを知りたいです。たとえば、ROWVERSIONテーブルなどの「現在」を取得できますか?

回答:


14

いいえ、ありません。すべてのトランザクションからのすべての更新は、「最終更新日」を追跡する1つのレコードを更新しようとするため、あらゆる種類の「最終更新日」の追跡は重大なパフォーマンス問題になります。これは事実上、1つのトランザクションのみがいつでもテーブルを更新でき、他のすべてのトランザクションは最初のトランザクションがコミットするのを待たなければならないことを意味します。完全なシリアル化。最後の更新がいつ行われたかを知るためだけに、このようなパフォーマンスのペナルティを我慢する管理者/開発者の数はおそらく少ないでしょう。

そのため、カスタムコードを介して処理することに取り残されます。これは、代替(ログレコードから検出)がトランザクションレプリケーション(またはCDC alter-ego)専用の特権であるため、トリガーを意味します。「最終更新日」列で追跡しようとすると、上記のシリアル化の問題に直面することに注意してください。更新の同時実行が重要な場合は、キューメカニズムを使用する必要があります(トリガーはINSERTを使用し、プロセスは挿入された値を集計して「最終更新日時」を作成します)。現在のIDを盗み見したり、sys.dm_db_index_usage_statsを検索したりするような「巧妙な」解決策をごまかそうとしないでください。また、Railsのタイムスタンプのように、レコードごとの「updated_at」列もあります。

「軽量」の代替手段はありますか?実際には1つありますが、それがあなたのために働くかどうかを言うのは難しく、それを正しくするのは難しいです:Query Notifications。クエリ通知があれば、それは、通知を設定します正確に、というん任意のデータは変更を持って、あなたがあなたのクエリをリフレッシュする必要があります。ほとんどの開発者はSqlDependencyとしての.Netインカネーションのみに精通していますが、クエリ通知、データの変更を検出するための長期間存続するメカニズムとして使用できます。真の変更追跡と比較すると、非常に軽量であり、そのセマンティクスはニーズ(何か、何か、変更されているため、クエリを再実行する必要があります)に近くなります。

しかし、最後に、あなたの代わりに、私は本当に私の仮定を再考し、図面に戻ります。おそらく、ログ配布またはレプリケーションを使用して、別のサーバーでレポートデータベースをセットアップできます。行間で読んだことは、適切なETLパイプラインと分析データウェアハウスが必要だということです...


それでは、Microsoftが提供する情報に頼ることができない場合、なぜsys.dm_db_index_usage_statsを作成するのが面倒なのでしょうか?
クレイグエフレイン

変更の追跡用に設計されたDMVではありません。意図した目的、つまりパフォーマンスチューニングに対して非常に信頼性があります。
レムスルサヌ

8

ここでは、ゲームに2年遅れているように見えますが、実際には、あなたが求めていることを行う非常に軽量な方法があります。

役立つ2つのSQL Serverメカニズムがあります。最終的な解決策は、この2つのハイブリッドです。

変更追跡。SQL Serverには、特定のテーブルを監視下に置き、変更された行(主キー値による)と変更の種類(挿入、更新、削除)のみを記録する機能があります。一連のテーブルで変更検出を設定すると、軽量クエリを使用して、最後にチェックしてからテーブルに変更が加えられたかどうかを確認できます。オーバーヘッドは、追加の単純なインデックスを維持するのとほぼ同じです。

Rowversion /タイムスタンプ。これは8バイトのvarbinary列タイプ(BigIntにキャスト可能)であり、1つを含む行が挿入または更新されるたびにデータベース全体でインクリメントされます(削除には役立ちません)。これらの列にインデックスを付けた場合、MAX(timestamp)を最後に評価されてからの値と比較することで、行データが変更されたかどうかを簡単に知ることができます。値は単調に増加するため、これにより、新しい値が最後にチェックしたときよりも大きい場合にデータが変更されたことを確実に示すことができます。


7

ソースが挿入専用の場合、IDENTITY列を指定します。データ転送を行うとき、書き込まれた最高値を記録します。次の転送では、前の転送で記録された値よりも大きい値のクエリのみが必要です。これは、ログレコードをデータウェアハウスに転送するために行います。

更新可能な行の場合、「ダーティ」フラグを追加します。クリーン、ダーティ、削除の3つの値があります。日常のクエリでは、フラグが「deleted」に設定されている行を省略する必要があります。これは、メンテナンス、テスト、および実行に費用がかかります。大きなクエリの後、削除対象としてマークされているすべての行を削除し、他のすべての行のフラグをリセットする必要があります。これはうまくスケーリングしません。

変更データキャプチャのより軽い代替手段は、変更追跡です。変更され値はわかりませんが、最後にクエリが実行されてから行が変更されただけです。組み込み関数により、変更された値の取得と追跡の管理が容易になります。CTを使用して、1日あたり約100,000件の変更を1億行のテーブルで処理することに成功しました。

クエリ通知は、結果セットのレベルでさらに高いレベルで機能します。概念的には、ビューを定義するようなものです。SQL Serverは、そのビューから返された行が変更されたことを検出すると、アプリケーションにメッセージを送信します。変更された行数や列は表示されません。「何かが起こった」という簡単なメッセージだけがあります。問い合わせて対応するのはアプリケーション次第です。ご想像のとおり、実際にはそれよりもはるかに複雑です。クエリの定義方法には制限があり、変更されたデータ以外の条件に対して通知が発生する場合があります。通知が発生すると、削除されます。その後、関心のあるアクティビティがさらに発生すると、それ以上メッセージは送信されません。

OPの質問のコンテキストでは、QNにはセットアップのオーバーヘッドが低く、実行時のコストが少ないという利点があります。厳密なsubscribe-message-reactレジームを確立して維持することは、かなりの努力になるかもしれません。データテーブルは大きいため、頻繁に変更される可能性が高く、ほとんどの処理サイクルで通知が発生する可能性が高いことを意味します。CTまたはCDCの場合のように、変更された差分の増分処理は不可能であることを示すものがないため。誤ったトリガーによるオーバーヘッドは面倒ですが、最悪の場合でも、高価なクエリを現在よりも頻繁に実行する必要はありません。


3

SqlTableDependency

SqlTableDependencyは、SQL Serverデータベースのテーブルレコード値を含む通知にアクセスするための高レベルの実装コンポーネントです。

SqlTableDependencyは、指定されたデータベーステーブルの内容が変更されたときに通知を受信するために使用される汎用C#コンポーネントです。

.NET SqlDepenencyとの違いは何ですか?

基本的に、主な違いは、SqlTableDependencyは、挿入、変更、または削除されたレコードの値を含むイベントと、テーブルで実行されるDML操作(挿入/削除/更新)を送信することです。データベーステーブル、彼らは何かが変わったと言うだけです。

GITHUBプロジェクトご覧ください


1

予想される更新がインデックスに影響する場合(およびその場合のみ)、システムテーブルsys.dm_db_index_usage_statsを使用して、問題のテーブルのインデックスに対する最後の更新を検出できます。last_user_updateフィールドを使用します。

たとえば、最新の更新されたテーブルを取得するには:

select
    object_name(object_id) as OBJ_NAME, *
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
order by
    dm_db_index_usage_stats.last_user_update desc

または、特定の日付以降に特定のテーブルが変更されたかどうかを確認するには:

select
    case when count(distinct object_id) > 0 then 1 else 0 end as IS_CHANGED
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
    and object_id = object_id('MY_TABLE_NAME')
    and last_user_update > '2016-02-18'

上記のRemusのコメントについてどう思いますか?「現在のIDを盗み見たり、sys.dm_db_index_usage_statsを検索したりするような「賢い」解決策でごまかそうとしないでください。」(彼の答えの下にある彼のコメントも参照してください。)
ファビアンシュミード

1
@FabianSchmied Interesting-答えを追加したときに、このユースケースでは信頼性がないことを示すために、Remusの別の答え以外に信頼できるものが見つからないことを見ていませんでした。のMSページにdm_db_index_operational_statsは問題が表示されます(メタデータキャッシュがクリアされるとクリアされます)が、は表示されませんdm_db_index_usage_stats。私が見つけた唯一の問題は、インデックスの再構築、サーバーの再起動、使用状況の統計をクリアするデータベースのデタッチでした。これに関する実証された情報を見ることに興味があるでしょう。
ジェフ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.