SQL:改变实体跟踪

时间:2016-07-18 12:01:44

标签: sql entity tracking trace

问题:

我应该使用什么方法来通知一个数据库有关对另一个数据库中的表所做的更改。注意:每个语句级事件我需要一个通知,这包括在一个中执行插入,更新和删除的merge语句。

背景

我们正在与第三方合作,将一些数据从一个系统传输到另一个系统。这里有两个感兴趣的数据库,一个是第三方填充规范化的分段数据,另一个是数据库,它将填充去规范化的后处理数据。我已经创建了MERGE脚本,这些脚本可以将来自这些临时表的数据处理和转移到我们闪亮的非规范化版本中,并且我已经编写了一个管理数据依赖关系的框架,以便查找表在主要数据等之前填充。

我需要一种可靠的方式来通知登台表何时更新,以便我的导入脚本能够自动运行。

考虑的方法:

SQL DML Triggers

我最初创建了一个通用触发器,它通过服务代理将更改信息发送到非规范化数据库,但是此触发器触发三次,一次用于插入,更新和删除,因此发送三个不同的消息,这导致导入过程到运行三次以进行单个数据更改。应该注意的是,这些登台表也使用SQL Server中的MERGE功能进行更新,因此可以在单个语句中处理。

SQL Query Notification

这看起来非常适合我需要的东西,但是无论如何似乎都没有从SQL Server中订阅通知,这只能用于通知用.net写的应用层的变更。 。我想我可以通过CLR集成来管理这个,但是我仍然需要将通知驱动到处理数据库以触发导入过程。这似乎是我最好的选择,虽然它会冗长,难以调试和监控,并且可能使一个简单的问题复杂化。

SQL Event Notification

这将是完美的,尽管似乎不适用于DML,无论您在MS文档中找到什么。 create event notification命令为event_type提供单个参数,因此可以认为是在数据库级别操作。 DML在实体级别运行,并且似乎无论如何都不会使用定义的语法来定位特定实体。

SQL Change Tracking

这似乎捕获了数据库表上的更改,但是在行级别上,这似乎对我的要求太过沉重。我只需要知道发生了变化,我对哪些行或多少行并不感兴趣,除此之外我还需要将其转换为触发导入过程的事件。

SQL Change Data Capture

这是更改跟踪的扩展,并记录行级别更改的更改和历史记录。这又是太详细了,仍然让我把这变成某种通知的问题,以便可以启动导入过程。

SQL Server Default Trace / Audit

这似乎需要一个目标,该目标必须是Windows应用程序/安全事件日志或IO上的文件,我很难监视和挂钩以进行更改。

ADDITIONAL

如果只触发了一次触发器,那么我的基于触发器的方法会非常有效。我考虑创建一个表来记录三个DML命令中的第一个,然后可以用它来暂停其他两个触发器操作中的信息发布,但是我可以合理地确定所有三个DML触发器(插入,更新)删除)并行激发这种方法是徒劳的。

任何人都可以建议一个合适的方法,理想情况下不使用预定的工作来检查更改。感激地收到任何建议。

1 个答案:

答案 0 :(得分:0)

这种最简单的方法是创建一个辅助表来记录触发器代码的运行时间。

$.getJSON

触发器按顺序运行,因此即使将merge语句应用于现有表,insert,update和delete触发器代码也会依次运行。

第一次输入触发器时,我们可以写入此暂停表以记录事件,然后执行需要执行的代码。

我们第二次输入触发器时,我们可以检查记录是否已经存在,从而阻止执行任何进一步的陈述。

CREATE TABLE [service].[SuspendTrigger]
(

    [Index]     [int] IDENTITY(1,1)     NOT NULL,
    [Name]      [nvarchar](200)         NOT NULL,
    [DateTime]  [datetime]              NOT NULL,
    [SPID]      [int]                   NOT NULL,

 CONSTRAINT [pk_suspendtrigger_index] PRIMARY KEY CLUSTERED 
(
    [Index] ASC
) ON [PRIMARY]
) ON [PRIMARY]

我们现在需要做的就是在暂停表中的[datetime]字段上创建一个索引,以便在检查期间使用它。我可能还会创建一份工作来清除任何超过几分钟的条目,以尝试保持内容不变。

无论哪种方式,这都提供了一种方法,可确保每次表级修改时仅生成通知。

如果你对这些消息内容感兴趣,那么......

alter trigger [dbo].[trg_ADDRESSES]
       on  [dbo].[ADDRESSES]
       after insert, update, delete
    as 
    begin
        set nocount on;

        -- determine the trigger action - not trigger may fire
        -- when nothing in either update or delete table
        ------------------------------------------------------
        declare @action as nvarchar(6) = (case  when (  exists ( select top 1 1 from inserted   )
                                                    and exists ( select top 1 1 from deleted    ))  then N'UPDATE'
                                                when    exists ( select top 1 1 from inserted   )   then N'INSERT'
                                                when    exists ( select top 1 1 from deleted    )   then N'DELETE'     
                                            end)

        -- check for valid action
        -------------------------
        if @action is not null
            begin
                if not exists ( select  * 
                            from    [service].[SuspendTrigger] as [suspend]
                            where   [suspend].[SPID] = @@SPID
                            and     [suspend].[DateTime] >= dateadd(millisecond, -300, getdate())
                        )
                begin

                    -- insert a suspension event
                    -----------------------------
                    insert into [service].[SuspendTrigger] 
                    (
                        [Name]      , 

                        [DateTime]  , 
                        [SPID]
                    )
                    select  object_name(@@procid)   as [Name]       ,
                            getdate()               as [DateTime]   ,
                            @@SPID                  as [SPID]

                    -- determine the message content to send
                    ----------------------------------------
                    declare @content xml = (

                        select  getdate()               as [datetime]                   ,
                                db_name()               as [source/catelogue]           ,
                                'DBO'                   as [source/table]               ,
                                'ADDRESS'               as [source/schema]              ,
                            (select     [sessions].[session_id]             as [@id]                        ,
                                        [sessions].[login_time]             as [login_time]                 ,
                                        case when ([sessions].[total_elapsed_time] >= 864000000000) then
                                            formatmessage('%02i DAYS %02i:%02i:%02i.%04i', 
                                                (([sessions].[total_elapsed_time]    / 10000 / 1000 / 60 / 60 / 24)),
                                                (([sessions].[total_elapsed_time]    / (1000*60*60)) % 24),
                                                (([sessions].[total_elapsed_time]    / (1000*60)) % 60),
                                                (([sessions].[total_elapsed_time]    / (1000*01)) % 60),
                                                (([sessions].[total_elapsed_time]    ) % 1000)) 
                                        else 
                                            formatmessage('%02i:%02i:%02i.%i', 
                                                (([sessions].[total_elapsed_time]    / (1000*60*60)) % 24),
                                                (([sessions].[total_elapsed_time]    / (1000*60)) % 60),
                                                (([sessions].[total_elapsed_time]    / (1000*01)) % 60),
                                                (([sessions].[total_elapsed_time]    ) % 1000))     
                                        end                                 as [duration]                   ,
                                        [sessions].[row_count]              as [row_count]                  ,
                                        [sessions].[reads]                  as [reads]                      ,   
                                        [sessions].[writes]                 as [writes]                     ,
                                        [sessions].[program_name]           as [identity/program_name]      ,
                                        [sessions].[host_name]              as [identity/host_name]         ,
                                        [sessions].[nt_user_name]           as [identity/nt_user_name]      ,   
                                        [sessions].[login_name]             as [identity/login_name]        ,
                                        [sessions].[original_login_name]    as [identity/original_name]     

                                from [sys].[dm_exec_sessions] as [sessions]
                                where [sessions].[session_id] = @@SPID
                                for xml path('session'), type) 

                            for xml path('persistence_change'), root('change_tracking'))

                            -- holds the current procedure name
                            -----------------------------------
                            declare @procedure_name nvarchar(200) = object_name(@@procid)

                            -- send a message to any remote listeners
                            -----------------------------------------
                            exec [service].[usp_post_content_via_service_broker] @MessageContentType = 'Source Data Change', @MessageContent = @content, @CallOriginator = @procedure_name

                end
            end
        end
    GO;

我可以发送触发通知的操作,但我只对这个表中某些数据发生了变化的事实感兴趣。