web-dev-qa-db-ja.com

SQL Server 2005/2008の非同期トリガー

挿入、更新、削除のたびに監査のために、大量のデータを操作して変更追跡テーブルに挿入するトリガーがあります。

このトリガーは非常にうまく機能します。これを使用することで、すべてのトランザクションのビジネス要件に従って、必要なoldvalues/newvaluesをログに記録できます。

ただし、ソーステーブルに多くの列があるsomeの場合、トランザクションが完了するまでに最大30秒かかる可能性があり、これは許容できません。

トリガーを非同期で実行する方法はありますか?任意の例。

35
Jose Basilio

トリガーを非同期で実行することはできませんが、トリガーで同期的にメッセージを SQL Service Broker キューに送信することができます。その後、ストアード・プロシージャーによってキューを非同期に処理できます。

26
Sean Reilly

これらの記事は、非同期監査にService Brokerを使用する方法を示しており、役立つはずです。

Service Brokerによる集中型非同期監査

Service Brokerの利点:クロスサーバーの多対1(1対多)のシナリオとそのトラブルシューティング方法

7
Mladen Prajdic

SQL Server 2014は Delayed Durability と呼ばれる非常に興味深い機能を導入しました。サーバーのクラッシュなどの壊滅的なイベントが発生した場合に、数行を失うことを許容できる場合は、実際のような状況でパフォーマンスを大幅に向上させることができます。

遅延トランザクションの持続性は、ディスクへの非同期ログ書き込みを使用して実現されます。トランザクションログレコードはバッファに保持され、バッファがいっぱいになるか、バッファフラッシュイベントが発生すると、ディスクに書き込まれます。遅延したトランザクションの耐久性により、システム内の待ち時間と競合の両方が減少します

テーブルを含むデータベースを最初に変更して、持続性を遅延させる必要があります。

ALTER DATABASE dbname SET DELAYED_DURABILITY = ALLOWED

次に、トランザクションごとに耐久性を制御できます。

begin tran

insert into ChangeTrackingTable select * from inserted

commit with(DELAYED_DURABILITY=ON)

トランザクションがクロスデータベースの場合、トランザクションは永続的としてコミットされるため、監査テーブルがトリガーと同じデータベースにある場合にのみ機能します。

許可されているのではなく強制的にデータベースを変更する可能性もあります。これにより、データベース内のすべてのトランザクションが永続的に遅延します。

ALTER DATABASE dbname SET DELAYED_DURABILITY = FORCED

持続性の遅延については、SQL Serverの予期しないシャットダウンと予期されるシャットダウン/再起動の間に違いはありません。壊滅的なイベントのように、データ損失を計画する必要があります。計画的なシャットダウン/再起動では、ディスクに書き込まれていない一部のトランザクションが最初にディスクに保存される場合がありますが、それを計画する必要はありません。計画的か計画外かにかかわらず、シャットダウン/再起動が致命的なイベントと同じようにデータを失うかのように計画します。

この奇妙な欠陥は、将来のリリースで解決されると期待されますが、それまでは、SQLサーバーの再起動またはシャットダウン時に「sp_flush_log」プロシージャを自動的に実行することをお勧めします。

4
moander

非同期処理を実行するには、Service Brokerを使用できますが、これが唯一のオプションではなく、CLRオブジェクトを使用することもできます。

以下は、別のプロシージャ(SyncProcedure)を非同期的に呼び出すストアドプロシージャ(AsyncProcedure)の例です。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Runtime.Remoting.Messaging;
using System.Diagnostics;

public delegate void AsyncMethodCaller(string data, string server, string dbName);

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void AsyncProcedure(SqlXml data)
    {
        AsyncMethodCaller methodCaller = new AsyncMethodCaller(ExecuteAsync);
        string server = null;
        string dbName = null;
        using (SqlConnection cn = new SqlConnection("context connection=true"))
        using (SqlCommand cmd = new SqlCommand("SELECT @@SERVERNAME AS [Server], DB_NAME() AS DbName", cn))
        {
            cn.Open();
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                reader.Read();
                server = reader.GetString(0);
                dbName = reader.GetString(1);
            }
        }
        methodCaller.BeginInvoke(data.Value, server, dbName, new AsyncCallback(Callback), null);
        //methodCaller.BeginInvoke(data.Value, server, dbName, null, null);
    }

    private static void ExecuteAsync(string data, string server, string dbName)
    {
        string connectionString = string.Format("Data Source={0};Initial Catalog={1};Integrated Security=SSPI", server, dbName);
        using (SqlConnection cn = new SqlConnection(connectionString))
        using (SqlCommand cmd = new SqlCommand("SyncProcedure", cn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@data", SqlDbType.Xml).Value = data;
            cn.Open();
            cmd.ExecuteNonQuery();
        }
    }

    private static void Callback(IAsyncResult ar)
    {
        AsyncResult result = (AsyncResult)ar;
        AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate;
        try
        {
            caller.EndInvoke(ar);
        }
        catch (Exception ex)
        {
            // handle the exception
            //Debug.WriteLine(ex.ToString());
        }
    }
}

非同期デリゲートを使用してSyncProcedureを呼び出します。

CREATE PROCEDURE SyncProcedure(@data xml)
AS
  INSERT INTO T(Data) VALUES (@data)

AsyncProcedureの呼び出し例:

EXEC dbo.AsyncProcedure N'<doc><id>1</id></doc>'

残念ながら、議会はUNSAFEの許可を必要とします。

2
Jesús López

履歴テーブルを作成します。メインテーブルの更新(/削除/挿入)中に、レコードの古い値(トリガーで削除された疑似テーブル)を履歴テーブルに挿入します。いくつかの追加情報も必要です(タイムスタンプ、操作タイプ、ユーザーコンテキストなど)。新しい値はとにかくライブテーブルに保持されます。

このようにして、トリガーは高速に実行され、遅い操作をログビューアーにシフトできます(手順)。

1
Arvo

変更を誰が行ったかなどを含む「too process」テーブルに挿入して、変更追跡のレコードにタグを付けることができるかどうか疑問に思います。

その後、別のプロセスが発生し、残りのデータを定期的にコピーすることができます。

1
GordyII

「うまく機能している」と「受け入れられない」の間には基本的な矛盾があります。

IMHOがマップしないOO手続き型アプリケーションでイベントを使用するのと同じ方法でトリガーを使用しようとしているように思えます。

30秒かかるトリガーロジック(いいえ、0.1秒以上)は機能しないものと呼びます。私はあなたが本当にあなたの機能性を再設計し、他の方法でそれをする必要があると思います。 「非同期にしたいなら」と言いますが、このデザインはどのような形でも意味がないと思います。

「非同期トリガー」に関する限り、基本的な根本的な矛盾は、BEGIN TRANステートメントとCOMMIT TRANステートメントの間にそのようなものを含めることができないということです。

1
dkretz

SQL Server 2008以降では、CDC機能を使用して、変更を自動的にログに記録できます。これは純粋に非同期です。詳細は こちら をご覧ください

0
Nithin Chandran

私が知っていることではありませんが、ベーステーブルにも存在する値を監査テーブルに挿入していますか?その場合は、変更のみを追跡することを検討できます。したがって、挿入は変更時間、ユーザー、エクストラ、および一連のNULL(実際には変更前の値)を追跡します。更新には、変更時刻、ユーザーなど、および変更された列の変更前の値のみが含まれます。削除には、atなどの変更とすべての値があります。

また、ベーステーブルごとに監査テーブルを持っていますか、それともDBの監査テーブルを1つ持っていますか?もちろん、後者の方が、各トランザクションが1つのテーブルに書き込もうとするため、待機が発生しやすくなります。

0
Karl

あなたのトリガーは、すべてのテーブルのすべての変更を1か所に記録するように設計されたこれらの一般的なcsv/text生成トリガーのものであると思います。理論的には良いかもしれませんが(おそらく...)、維持して実際に使用することは困難です。

非同期で実行できる場合(後で再びログを記録するためにデータをどこかに保存する必要がある場合)は、監査を行わず、使用する履歴もありません。

おそらく、トリガーの実行計画を見て、最も時間がかかっているビットを確認できますか?

たとえば、監査方法をテーブルごとに変更できますか?現在のログデータを関連するテーブルに分割できます。

0
gbn