web-dev-qa-db-ja.com

トランザクションが終了したときに発生するトリガー/イベント

約15の異なるテーブルから取得するクエリがあります。これをxml/jsonに格納するテーブルに具体化したいと考えています。 (パフォーマンスを向上させるため。)

私が抱えている問題は、これらのテーブルがいくつかのプロセスによって更新されることです。可能であれば、これをSQL Serverで維持する方法を探しています。

理想的には、トランザクションがコミットする直前に起動するトリガーがSQL Serverにあり、影響を受けるテーブルとレコードを確認して、「結果」テーブルを更新する必要があるかどうかを知ることができれば、私はそれを気に入っています。

SQL Serverにそのようなものはありますか?

注:INSTEAD OFトリガーの使用を検討しましたが、トランザクション内のテーブルの順序を知る方法がないため、トランザクションが15個すべてのテーブルを更新する場合、「結果」テーブルを更新します15同じ行の時間。

3
Vaccano

男、すみません。

私はあなたが考案したような簡単な解決策はないと言って答えています。

あなたは私たちに非常に興味深い挑戦をしました。

SQL Serverエンジンだけに依存するコミットされたトランザクションの余波を収集する方法がないことを確認するために、過去4時間に研究と演習を行ってきました。

ソリューションアーキテクチャの全体像を見せていただければ、最小の労力でソリューションを考案します。

たとえば、シナリオでは:

  • 1つまたは2つのADO.Netアプリケーション
  • 1つまたは2つのEntityFrameworkアプリケーション
  • 1つまたは2つのSSISジョブ

これらのテーブルのデータを変更する可能性がある場合は、次のようにします。

  • 調整されたことを示すために、各テーブルにトリガーを作成する
  • フラグが立てられたデータを処理するSPを呼び出す各アプリケーションでインタセプションを作成します

そして、遮断点について、私は以下を行います:

  • aDO.Netアプリで-AdoDbConnectionを置き換え/オーバーライドし、VSに依存してコードを迅速かつ安全にリファクタリングする
  • eFアプリ-DbContextからのSaveChangesのオーバーライド
  • sSISジョブ-各ジョブの最後に呼び出しを含める

以下に紹介します:

  • 処理するイベントを登録するテーブルのスキーマ
  • ターゲットテーブルのトリガーを生成するスクリプト
  • 上記の拡張ポイントによって呼び出される、ストアドプロシージャの処理のスニペット

処理するイベントのレジストリ

後処理するレコードを登録するには、次のような単一のテーブルを設計します。

IF EXISTS(SELECT * FROM sys.tables WHERE name = 'TBL_2PROC') DROP TABLE TBL_2PROC
go

CREATE TABLE TBL_2PROC(
    session_id     INT,          -- the session ID - it's expected each app transaction (or session) to have
                                 -- its own, exclusive, not shared, connection, even if pooled
    transaction_id BIGINT,       -- to assure grouping -> (session_id, transaction_id)
    event          VARCHAR(255), -- string stating an INSERT, UPDATE or DELETE
    tblName        VARCHAR(255), -- name of the table affected
    keyId          VARCHAR(255)  -- value of the (single) id column, converted to varchar
)
go

廃止されたトリガーが存続しないようにする

実際のトリガーを作成する前に、古いトリガーが残っていないことを確認する必要があります。

-- Clean-up existing ones ------------------------------------------------------
--------------------------------------------------------------------------------

DECLARE cCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT name
FROM sys.triggers
WHERE name LIKE 'TR_2PROC%'
--
DECLARE @trName VARCHAR(255)
--
DECLARE @sql NVARCHAR(MAX)

OPEN cCursor
FETCH cCursor INTO @trName

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @sql = 'DROP TRIGGER [' + @trName + ']'

    EXECUTE sp_executesql @sql

    FETCH cCursor INTO @trName
END
go

ターゲットのパラメーター化

プログラムで必要なトリガーを作成する前に、どのトリガーになるかを宣言する必要があります。

-- Parameterize the target tables and theirs key columns -----------------------
--------------------------------------------------------------------------------

CREATE TABLE #to_log(tblName VARCHAR(255), keyName VARCHAR(255))
go

INSERT INTO #to_log VALUES('SomeTable',      'Id')
INSERT INTO #to_log VALUES('SomeOtherTable', 'OtherId')
go

プログラムで必要なトリガーを作成する

ターゲットが設定されているため、このスクリプトはトリガーを作成します。

-- Setup triggers --------------------------------------------------------------
--------------------------------------------------------------------------------

DECLARE @tblName VARCHAR(255)
DECLARE @keyName VARCHAR(255)
--
DECLARE @sql NVARCHAR(MAX)
--

DECLARE cCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT tblName, keyName
FROM #to_log

OPEN cCursor
FETCH cCursor INTO @tblName, @keyName

WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @triggerName VARCHAR(255)

    SET @triggerName = '[TR_2PROC_' + @tblName + ']'

    -- Drop if exists ----------------------------------------------------------

    SET @sql = N'
        IF EXISTS(SELECT * FROM sys.triggers WHERE name = ''' + @triggerName + ''')
        DROP TRIGGER ' + @triggerName

    EXECUTE sp_executesql @sql

    -- Create ------------------------------------------------------------------

    SET @sql = N'
        CREATE TRIGGER ' + @triggerName + '
        ON [' + @tblName + ']
        AFTER INSERT, UPDATE, DELETE
        AS
        BEGIN
            DECLARE @session_id     INT
            DECLARE @transaction_id BIGINT

            SELECT
                @session_id     = session_id,
                @transaction_id = transaction_id
            FROM sys.dm_tran_session_transactions 
            WHERE session_id = @@spid

            INSERT INTO TBL_2PROC
            SELECT
                @session_id, @transaction_id,
                CASE
                    WHEN del.[' + @keyName + '] IS NULL THEN ''INSERT''
                                                        ELSE ''UPDATE''
                END,
                ''' + @tblName + ''',
                CONVERT(VARCHAR(255), ins.[' + @keyName + '])
            FROM
                inserted ins
                    LEFT OUTER JOIN deleted del
                    ON del.[' + @keyName + '] = ins.[' + @keyName + ']

            INSERT INTO TBL_2PROC
            SELECT
                @session_id, @transaction_id,
                ''DELETE'',
                ''' + @tblName + ''',
                CONVERT(VARCHAR(255), del.[' + @keyName + '])
            FROM deleted del
            WHERE
                del.[' + @keyName + '] NOT IN(
                    SELECT ins.[' + @keyName + ']
                    FROM inserted ins
                )
        END'

    EXECUTE sp_executesql @sql

    FETCH cCursor INTO @tblName, @keyName
END
GO

いくつかのクリーンアップ

いくつかのクリーンアップが続きます:

-- Clean-up --------------------------------------------------------------------
--------------------------------------------------------------------------------

DROP TABLE #to_log
go

...そして、処理はここで行われます!

今、本当の取引。

最後になりましたが、少なくとも、処理手順を作成する仕事が残ります。

トリガーによってCOMMITをインターセプトできたとしても、処理される結果の出力はこれと非常に似ています。

IF EXISTS(SELECT * FROM sys.procedures WHERE name = 'SP_2PROC') DROP PROCEDURE SP_2PROC
GO

CREATE PROCEDURE SP_2PROC AS
BEGIN
    -- Important!
    --
    -- Use WITH(NOLOCK) to avoid dead-locks
    --
    -- Do NOT use SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED here,
    -- unless you know what you are doing
    -- (this isolation level is often safe in other contexts)

    -- You, now, shall build your process up from here, based on the
    -- query below

    SELECT *
    FROM
        TBL_2PROC WITH(NOLOCK)
    WHERE session_id = @@SPID
END
GO