[英]Asynchronous Triggers in SQL Server 2005/2008
我有觸發器,可以在每次插入、更新和刪除時操作並將大量數據插入到更改跟蹤表中,以便進行審計。
這個觸發器很好地完成了它的工作,通過使用它,我們能夠根據每個事務的業務需求記錄所需的舊值/新值。
但是,在某些情況下,源表有很多列,事務可能需要 30 秒才能完成,這是不可接受的。
有沒有辦法讓觸發器異步運行? 任何例子。
您不能讓觸發器異步運行,但可以讓觸發器同步向SQL Service Broker隊列發送消息。 然后可以通過存儲過程異步處理隊列。
這些文章展示了如何使用服務代理進行異步審計,應該很有用:
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”過程可能是明智的。
要執行異步處理,您可以使用 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 許可。
從 sql server 2008 開始,您可以使用CDC
功能自動記錄更改,這完全是異步的。 在此處查找更多詳細信息
我想知道您是否可以通過插入“太進程”表(包括誰進行了更改等)來標記更改跟蹤的記錄。
然后另一個進程可能會出現並定期復制其余數據。
顯然,“做得很好”和“不可接受”之間存在基本沖突。
在我看來,您正在嘗試以與在 OO 程序應用程序中使用事件相同的方式使用觸發器,恕我直言,它沒有映射。
我會稱任何需要 30 秒的觸發邏輯 - 不,超過 0.1 秒 - 都無法正常工作。 我認為您確實需要重新設計您的功能並以其他方式進行。 我會說“如果你想讓它異步”,但我認為這種設計在任何形式上都沒有意義。
至於“異步觸發器”,基本的根本沖突是您永遠不能在 BEGIN TRAN 和 COMMIT TRAN 語句之間包含這樣的東西,因為您已經忘記了它是否成功。
創建歷史表。 在更新(/刪除/插入)主表的同時,將記錄的舊值(觸發器中刪除的偽表)插入到歷史表中; 還需要一些額外的信息(時間戳、操作類型,可能是用戶上下文)。 無論如何,新值都保留在實時表中。
這種方式觸發器運行得很快(呃),您可以將慢速操作轉移到日志查看器(過程)。
不是我所知道的,但是您是否將值插入也存在於基表中的審計表中? 如果是這樣,您可以考慮僅跟蹤更改。 因此,插入將跟蹤更改時間、用戶、額外和一堆 NULL(實際上是之前的值)。 更新將僅具有更改時間、用戶等和更改列的之前值。 刪除具有更改 at 等和所有值。
另外,對於每個基表,您是否有一個審計表或一個數據庫審計表? 當然,后者更容易導致等待,因為每個事務都試圖寫入一個表。
我懷疑您的觸發器屬於這些通用 csv/文本生成觸發器,旨在將所有表的所有更改都記錄在一個地方。 理論上很好(也許......),但在實踐中難以維護和使用。
如果您可以異步運行(這仍然需要將數據存儲在某處以便稍后再次記錄),那么您就沒有進行審計,也沒有可以使用的歷史記錄。
也許您可以查看觸發器執行計划,看看哪個位花費的時間最長?
你能改變你的審計方式嗎,比如,每張表? 您可以將當前日志數據拆分到相關表中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.