繁体   English   中英

SQL Server 2005/2008 中的异步触发器

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM