Sql-Server

事務完成時觸發的觸發器/事件

  • July 18, 2016

我有一個從大約 15 個不同的表中提取的查詢。我希望將其具體化為一個將其儲存在 xml/json 中的表。(以提高性能。)

我遇到的問題是這些表是由幾個程序更新的。如果可能的話,我正在尋找一種將其保存在 SQL Server 中的方法。

理想情況下,如果 SQL Server 有一個在事務送出之前觸發的觸發器,我會喜歡它,這樣我就可以查看受影響的表和記錄,並知道我是否需要更新“結果”表。

SQL Server 中有類似的東西嗎?

注意:我考慮過使用INSTEAD OF觸發器,但我無法知道事務中表的順序,所以如果事務更新了所有 15 個表,那麼我將為同一行更新“結果”表 15 次.

伙計,我非常抱歉。

我正在“回答”說沒有像您設計的那樣簡單的解決方案。

你向我們提出了一個非常有趣的挑戰。

在過去的四個小時裡,我一直在做研究和練習,以確保沒有辦法單獨收集依賴於 SQL Server 引擎的已送出事務的後果。

如果您可以向我們展示解決方案架構的宏偉視圖,我們將設計一個最省力的解決方案。

例如,在場景中:

  • 一個或兩個 ADO.Net 應用程序
  • 一個或兩個 EntityFramework 應用程序
  • 一個或兩個 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通過觸發器攔截 a,要處理的結果輸出也將與此非常相似。

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

引用自:https://dba.stackexchange.com/questions/105808