web-dev-qa-db-ja.com

SQL Serverテーブルの変更の検出

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

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

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

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

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

13
Fabian Schmied

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

そのため、カスタムコードを使用して処理することはできません。代替手段(ログレコードからの検出)はトランザクションレプリケーション(または [〜#〜] cdc [〜#〜] alter-ego)のためにのみ予約されている特権であるため、これはトリガーを意味します。 「最終更新日」列を介して追跡しようとすると、上記のシリアル化の問題に正確に直面することに注意してください。更新の同時実行性が重要な場合は、キューメカニズムを使用する必要があります(トリガーはINSERTを使用し、プロセスは挿入された値を集計して「最終更新時刻」を定式化します)。現在のIDをこっそりと調べたり、検索したりするなど、「賢い」ソリューションでチートしようとしないでください sys.dm_db_index_usage_stats 。また、Railsタイムスタンプのような)レコードごとの 'updated_at'列は、削除を検出しないため機能しません...

「軽量」の代替品はありますか?実際には1つありますが、うまくいくかどうかを判断するのが難しく、正しく取得するのが困難です Query Notifications 。クエリ通知はそれを正確に行い、anyデータに変更があり、クエリを更新する必要がある場合に通知を設定します。ほとんどの開発者は、SqlDependencyとしての.Netインカネーションにのみ精通していますが、クエリ通知canは、データの変更を検出するための長期にわたる永続的なメカニズムとして使用できます。真の変更追跡と比較すると、それは非常に軽量になり、そのセマンティクスはニーズに近づきます(何か、anything、変更されたため、クエリを再実行する必要があります)。

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

14
Remus Rusanu

私はここでゲームに2年遅れているようですが、確かにあなたが求めていることを実行するためのかなり軽量な方法があります。

役立つ2つのSQL Serverメカニズムがあります。あなたの究極の解決策は、2つのハイブリッドかもしれません。

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

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

8
Curt

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

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

Change Data Captureのより軽い代替手段は Change Tracking です。 whatの値が変更されたことが通知されず、行が最後にクエリされてから変更されたことがわかります。組み込み関数により、変更された値の取得と追跡の管理が容易になります。 CTを使用して、1億行のテーブルで1日あたり約100,000件の変更を処理することに成功しました。

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

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

7
Michael Green

SqlTableDependency

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

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

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

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

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

期待する更新がインデックスに影響する場合(およびのみの場合)、システムテーブルを使用できます 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'
1
Geoff