从常规触发器调用 SQLCLR 存储过程而不是立即部署 SQLCLR 触发器有什么好处吗?
嗯,有一些差异。这些差异与利弊之间的关系取决于每种情况的具体情况。
T-SQL 触发器调用 SQLCLR 对象(存储过程或函数)
T-SQL 触发器可以通过类似于以下内容的查询来确定它们附加到哪个表:
SELECT ss.[name] AS [SchemaName], so.[name] AS [TableName]
FROM [sys].[objects] so
INNER JOIN [sys].[schemas] ss ON ss.[schema_id] = so.[schema_id]
WHERE so.[object_id] = (SELECT so2.[parent_object_id]
FROM [sys].[objects] so2
WHERE so2.[object_id] = @@PROCID);
从 T-SQL 触发器调用的 SQLCLR 对象不能直接访问INSERTED
和DELETED
伪表(对于 T-SQL 存储过程甚至EXEC()
调用也是如此)。这些表可以复制到本地临时表中,然后可以从 SQLCLR 对象(或 T-SQL 存储过程或EXEC()
调用)中读取,但是创建表需要更多的时间和 I/O,从伪读取-table(s),然后将其再次写入tempdb
.
SQLCLR 触发器
SQLCLR 触发器可以通过动态 SQLINSERTED
与DELETED
伪表和伪表交互(这是 T-SQL 触发器无法做到的)。当然,如果 SQLCLR 代码SELECT
首先需要从伪表中提取,那么您几乎需要使用 SQLCLR 触发器(否则执行愚蠢的 copy-pseudo-tables-to-local-temp-tables 技巧)。
SQLCLR 触发器无法访问@@PROCID
(即使创建SqlConnection
using Context Connection = true;
),因此实际上没有简单的方法来确定正在修改的 Table 导致触发器被触发。我强调“简单”,因为可以(虽然我没有测试过)使用 T-SQL 触发器,设置为表上的“第一个”触发器,CONTEXT_INFO
用表名设置,但是你不能使用CONTEXT_INFO
其他任何事情。可能还有另一种方法我几乎已经解决了,但还没有测试过(当我有机会测试时,如果它有效,我会尽量记住在此处更新说明的链接)。
我需要在非常大的表中收到有关特定列更改 (StatusID) 的通知。当前使用了许多 Windows 服务。每个都监控自己的 StatusID,即向 db 查询特定的 StatusID:SELECT a,b,c FROM t WHERE StatusID = @status。
这绝对可以更好地处理,并且无需使用 SQLCLR。SQLCLR 可能很棒,但在必要时应该使用它。在这种情况下,您只需要一个队列表来记录表中更改的行的 PK 列。
CREATE TABLE dbo.TableChangeQueue
(
TableChangeQueueID INT IDENTITY(-2140000000, 1)
NOT NULL
CONSTRAINT [PK_TableChangeQueue] PRIMARY KEY,
[a] datatype_a,
[b] datatype_b,
[c] datatype_c
);
然后使用常规 T-SQL 触发器INSERT
进入该队列。类似于以下内容:
INSERT INTO dbo.TableChangeQueue ([a], [b], [c])
SELECT [a], [b], [c]
FROM INSERTED;
然后每个 Windows 服务都可以从队列表(而不是大表)中读取。处理完记录后,根据它们的TableChangeQueueID
值将它们从队列表中删除。OUTPUT
您可以使用语句中的子句同时读取和删除它们DELETE
,但如果过程失败,您不希望失去重新尝试处理它们的能力。
当然,这是一个简单的实现,如果这些行在处理之前被多次更新,则允许重复的键条目。如果这是一个问题(即,您需要键值是唯一的),那么在触发器中有处理该问题的方法。请注意,触发器不会更改 Windows 服务正在处理的数据,除非这不会出现问题。
这里有很多选项,但如果不知道流程是如何工作的,就无法具体说明。但无论如何,主要的一点是将更改StatusID
的 s 的处理与 DML 语句分开,因为触发器在内部系统生成的事务中运行。这就是为什么您不想在此触发器中立即处理更改的行,因为它很容易增加对表的阻塞,因为在触发器完成之前事务不会完成;如果触发器出错,则 DML 语句将回滚。