2017-04-11 38 views
1

我创建使用SQL CLR触发器在Microsoft SQL Server 2012的一个数据库同步引擎,这些触发器不调用存储过程或函数(从而有机会获得inserted和deleted伪表但无法访问@@ procid)。SQL CLR触发器 - 获取源表

差异here,以供参考。

这种“同步引擎”使用映射表来确定哪些表和字段映射是此同步作业。为了确定目标表和字段(从我的映射表),我需要从触发器本身获取源表名。我在Stack Overflow和其他网站上发现了很多答案,说这是不可能的。但是,我发现一个website,提供了一个线索:

潜在的解决方案:

using (SqlConnection lConnection = new SqlConnection(@"context connection=true")) { 
    SqlCommand cmd = new SqlCommand("SELECT object_name(resource_associated_entity_id) FROM sys.dm_tran_locks WHERE request_session_id = @@spid and resource_type = 'OBJECT'", lConnection); 
    cmd.CommandType = CommandType.Text; 
    var obj = cmd.ExecuteScalar(); 
} 

但这实际上返回正确的表名。

问:

我的问题是,如何可靠是这个潜在的解决方案? @@ spid实际上是否仅限于此单个触发器执行?或者是否有可能其他同时触发器会在此进程ID内重叠?它会在数据库中多次执行相同和/或不同的触发器吗?

从这些网站,它似乎进程ID,其实是限制在打开的连接,不重叠:hereherehere

这会不会是一种安全的方法,让我的源表?

为什么?

正如我已经注意到了类似的问题,但都没有对我的具体情况有效的答案(除了一个)。大部分这些网站上的意见,问:“为什么?”,为了抢占的是,这是为什么:

此同步引擎上的单个数据库上运行并且可将更改到目标表,改造与用户数据自定义的源到目标类型转换和解析,甚至可以使用CSharpCodeProvider执行也存储在这些映射表中的方法来转换数据。它已经建立,相当强大,并且对我们正在做的事情有很好的性能指标。我现在试图构建它以允许1:n表更改(包括扩展表需要与“主”表相同的ID),并且试图“代码化”代码。以前每个触发器都有一个硬编码的“目标表”定义,我使用映射表来确定源。现在我想获取源表并使用我的映射表来确定所有的目标表。这用于中等负载环境,并将更改推送到“更改订单簿”,单独的服务器进程将采用该更改以完成CRUD操作。

编辑

正如在评论中提到的,上面列出的查询是相当“前途未卜”。它会经常(例如在SQL Server重新启动后)返回像syscolpars或sysidxstats这样的系统对象。但是,似乎在dm_tran_locks表中,总是有一个相关的resource_type为'RID'(行ID)和相同的object_name。我这工作可靠到目前为止当前查询是以下(将更新,如果这变化的情况下高负载测试不工作):如果这是总是如此

select t1.ObjectName FROM (
    SELECT object_name(resource_associated_entity_id) as ObjectName 
    FROM sys.dm_tran_locks WHERE resource_type = 'OBJECT' and request_session_id = @@spid 
) t1 inner join (
    SELECT OBJECT_NAME(partitions.OBJECT_ID) as ObjectName 
    FROM sys.dm_tran_locks 
    INNER JOIN sys.partitions ON partitions.hobt_id = dm_tran_locks.resource_associated_entity_id 
    WHERE resource_type = 'RID' 
) t2 on t1.ObjectName = t2.ObjectName 

,我必须找到了在测试期间。

+1

这是可能的。它假设引擎将精确锁定一个对象,即触发器执行的表。也许这总是会发生,但你不能指望任何人给你保证。我不太关心“@@ spid” - 尽管MARS可以让多个语句在连接上处于活动状态,但这是一种虚假的并发。触发器执行不会重叠,它们会按顺序执行。你不应该担心其他陈述干扰。 –

+0

确保测试当您抛出事务时会发生什么,特别是在'SERIALIZABLE'('SELECT * FROM T WITH(HOLDLOCK)')下执行时。如果触发器在其他锁已被占用的事务下执行,会发生什么情况? –

+0

为了清楚起见,您试图使用一个触发器(或者,也许是一组代码)作为多个表的触发器? –

回答

0

这种潜在的解决方案有多可靠?

虽然我没有时间来建立一个测试用例来显示它不工作,我发现这种方法(即使考虑到在编辑部分查询)“前途未卜”(即不保证总是是可靠的)。

的主要问题是:

  • 级联(是否递归与否)触发处决
  • 用户(即显式/隐式)的交易
  • 子流程(即EXECsp_executesql

这些场景允许多个对象同时被锁定。

@@ SPID实际上是否仅限于此单个触发器执行?或者是否有可能其他同时触发器会在此进程ID内重叠?

和(从关于这个问题的评论):

我想我可以加入我查询了该sys.partitions并获得dm_trans_lock其类型为“RID”的一个对象名称将匹配我原来的查询中的一个。

这就是为什么它不应该完全可靠:会话ID(即@@SPID)对该连接上的所有请求都是不变的)。因此,所有子过程(即EXEC调用,sp_executesql,触发器等)将全部在相同的@@SPID/session_id上。因此,在子流程和用户事务之间,您可以非常轻松地获取多个资源上的锁定,这些资源全部位于同一个会话ID中。

我说“资源”而不是“OBJECT”或甚至“RID”的原因是锁可以发生在:行,页面,键,表,模式,存储过程,数据库本身等等。多于一个东西可以被认为是“OBJECT”,并且有可能你将拥有页面锁而不是行锁。

它会站起来对数据库中相同和/或不同触发器的多次执行吗?

只要这些执行发生在不同的会话中,那么它们就不是问题。

所有这些都说明了,我可以看到简单的测试将显示您的当前方法是可靠的。但是,添加更详细的测试也应该很简单,这些测试包括首先在另一个表上执行一些DML的显式事务,或者在一个表上具有触发器在其中一个表上执行一些DML等。

不幸的是,没有内置的机制可以提供@@PROCID为T-SQL触发器所做的功能。我想出了一个方案,应该允许获得SQLCLR触发器(考虑到这些不同的问题)的父表,但没有机会测试它。它要求使用设置为“第一个”触发器的T-SQL触发器来设置可由SQLCLR触发器发现的信息。

简单的形式可如果你是不是已经在使用它了别的使用CONTEXT_INFO构造(如果你还没有一个“第一”触发设置)。在这种方法中,您仍然会创建一个T-SQL触发器,然后使用sp_settriggerorder将其设置为“第一个”触发器。在此触发器中,您将SET CONTEXT_INFO添加到为@@PROCID的父表的名称。然后您可以在SQLCLR触发器的上下文连接中读取CONTEXT_INFO()。如果有多个级别的触发器,则CONTEXT INFO的值将被覆盖,因此读取该值必须是每个SQLCLR触发器中的第一件事。

+0

我知道刚刚够SQLCLR是危险的,但有没有什么办法来查询触发器代码中的SqlTriggerAttribute来梳理它的属性(在这种情况下,目标似乎相关)? –

+1

@BenThul在某种意义上是的。方法属性存储在程序集中,可以通过反射发现。但是,“目标”在编译时将成为硬编码值。所以在同一个'SqlTrigger'方法的所有用法中,这个值是相同的。获得不同值的唯一方法是重新编译,因此每个目标有一个方法,即使所有'SqlTrigger'方法都调用一个常用方法,这也会增加维护成本。我将不得不对此进行测试,看看它是否可以变成更易于管理的形式,并且如果TriggerContext符合预期。 –

+0

@srutzky谢谢。不幸的是,除了为每个使用通用方法的表使用单个触发器定义外,我没有办法做到这一点。我尝试从SqlTriggerAttribute获取信息,但那也不适用于我。你会怎么建议我得到那个?如果必须为每个表分别使用不同的触发器定义,我想在随后的通用方法调用中对表名进行硬编码并不困难。我只是不想停下来看看我能真正实现它的通用性。 – Greg