我正在使用Microsoft SQL Server 2012中的SQL CLR触发器创建数据库同步引擎。这些触发器不会调用存储过程或函数(从而可以访问INSERTED和DELETED伪表,但无法访问@@ PROCID)。
差异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实际上仅限于开放式连接,它不会重叠:here,here和{ {3}}
这是获取源表的安全方法吗?
为什么吗
我已经注意到类似的问题,但都没有针对我的具体情况的有效答案(除了那个)。这些网站上的大多数评论都会问"为什么?",为了先发制人,这就是原因:
此同步引擎在单个数据库上运行,可以将更改推送到目标表,使用用户定义的转换转换数据,自动源到目标类型转换和解析,甚至可以使用CSharpCodeProvider执行也存储在那些用于转换数据的映射表。它已经构建,非常强大,并且具有良好的性能指标,可用于我们正在做的事情。我现在正在尝试构建它以允许1:n表更改(包括扩展表需要与' master'表相同的ID)并且我正在尝试" genericise&#34 ;代码。以前每个触发器都有一个"目标表"定义硬编码在其中我使用我的映射表来确定源。现在我想获取源表并使用我的映射表来确定所有目标表。这用于中等负载环境,并将更改推送到变更订单簿"变更订单簿"一个单独的服务器进程选择完成CRUD操作。
修改
正如评论中所提到的,上面列出的查询完全是" iffy"。它经常(例如在SQL Server重启后)返回系统对象,如syscolpars或sysidxstats。但是,似乎在dm_tran_locks表中总是有一个关联的资源类型' 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
如果情况总是如此,我将不得不在测试期间找到它。
答案 0 :(得分:0)
这种潜在的解决方案有多可靠?
虽然我没有时间设置测试用例以证明它不起作用,但我发现这种方法(甚至考虑到编辑部分中的查询)“iffy”(即不保证 总是可靠)。
主要关注点是:
EXEC
和sp_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触发器中执行的第一件事。
答案 1 :(得分:0)
这是一个旧线程,但这是一个常见问题解答,我想我有一个更好的解决方案。本质上,它通过对列名进行散列并将散列与具有哈希表的CLR触发器的表的散列进行比较,从而使用插入或删除表的架构来查找基表。
下面的代码段-在某个时候,我可能会将整个解决方案放在Git上(当触发器触发时,它将向Azure Service Bus发送消息)。
private const string colqry = "select top 1 * from inserted union all select top 1 * from deleted";
private const string hashqry = "WITH cols as ( "+
"select top 100000 c.object_id, column_id, c.[name] "+
"from sys.columns c "+
"JOIN sys.objects ot on (c.object_id= ot.parent_object_id and ot.type= 'TA') " +
"order by c.object_id, column_id ) "+
"SELECT s.[name] + '.' + o.[name] as 'TableName', CONVERT(NCHAR(32), HASHBYTES('MD5',STRING_AGG(CONVERT(NCHAR(32), HASHBYTES('MD5', cols.[name]), 2), '|')),2) as 'MD5Hash' " +
"FROM cols "+
"JOIN sys.objects o on (cols.object_id= o.object_id) "+
"JOIN sys.schemas s on (o.schema_id= s.schema_id) "+
"WHERE o.is_ms_shipped = 0 "+
"GROUP BY s.[name], o.[name]";
public static void trgSendSBMsg()
{
string table = "";
SqlCommand cmd;
SqlDataReader rdr;
SqlTriggerContext trigContxt = SqlContext.TriggerContext;
SqlPipe p = SqlContext.Pipe;
using (SqlConnection con = new SqlConnection("context connection=true"))
{
try
{
con.Open();
string tblhash = "";
using (cmd = new SqlCommand(colqry, con))
{
using (rdr = cmd.ExecuteReader(CommandBehavior.SingleResult))
{
if (rdr.Read())
{
MD5 hash = MD5.Create();
StringBuilder hashstr = new StringBuilder(250);
for (int i=0; i < rdr.FieldCount; i++)
{
if (i > 0) hashstr.Append("|");
hashstr.Append(GetMD5Hash(hash, rdr.GetName(i)));
}
tblhash = GetMD5Hash(hash, hashstr.ToString().ToUpper()).ToUpper();
}
rdr.Close();
}
}
using (cmd = new SqlCommand(hashqry, con))
{
using (rdr = cmd.ExecuteReader(CommandBehavior.SingleResult))
{
while (rdr.Read())
{
string hash = rdr.GetString(1).ToUpper();
if (hash == tblhash)
{
table = rdr.GetString(0);
break;
}
}
rdr.Close();
}
}
if (table.Length == 0)
{
p.Send("Error: Unable to find table that CLR trigger is on. Message not sent!");
return;
}
….
HTH