我有一个用于审计的通用CLR触发器。为数据库中的每个可审计表创建此触发器。
触发器执行的第一个语句是获取与使用sys.dm_tran_locks
相关联的表。但这并不总是按预期工作。
我在数据库中有一个通用审计表,其结构如下:
CREATE TABLE [dbo].[audit](
[id] [int] IDENTITY(1,1) NOT NULL,
[user_name] [varchar](50) NULL,
[date_time] [datetime] NULL DEFAULT (getdate()),
[item_table] [varchar](50) NULL,
[item_id] [int] NULL,
[item_action] [int] NULL,
[description] [text] NULL)
审计表中的每一行表示对表中记录的更改,description
表包含受影响的所有列,并采用以下格式:
column1name|oldvalue|tovalue-column2name|oldvalue|tovalue
注意:|
和-
分隔符用于上述示例中的显示。
每个可审核表都有一个与之关联的触发器:
ALTER TRIGGER [dbo].[tr_table1_audit] ON [dbo].[table1]
WITH EXECUTE AS CALLER AFTER INSERT, DELETE, UPDATE
AS
EXTERNAL NAME [Triggers].[Audit].[InsertRecord]
CLR触发器如下:
public class Audit
{
[SqlTrigger]
public static void InsertRecord()
{
//1. Get triggers associated table name (not working correctly)
//2. Get affected columns
switch (triggerContext.TriggerAction)
{
case TriggerAction.Insert:
using (SqlDataAdapter adapter = new SqlDataAdapter(@"SELECT * FROM INSERTED", conn))
using (DataTable inserted = new DataTable())
{
adapter.Fill(inserted);
//1. Loop through inserted rows
//2. For each row, loop through columns
//3. Build string for the description column above
//4. Insert audit record
}
break;
//Update and delete here...
}
}
}
所以我考虑将代码从触发器移动到存储过程并将表名作为参数传递。
是否可以在存储过程中使用inserted和deleted表?
答案 0 :(得分:1)
首先,在任何情况下,都不要从触发器以外的任何事情进行审核。否则,您将错过对数据的一些更改。
接下来,您为什么使用CLR?使用普通代码无法做到这一点。 (顺便说一句,从来没有,永远不会在触发器中循环记录,学会使用基于集合的操作)
下一步。只有一个审计表是一个非常糟糕的想法,它将成为数据库中的热点,并可能导致性能问题。每个表都应该有一个审计表。
更好的想法是创建代码以在添加新表时创建新的审计表和审计触发器。我们所有的审计触发器都是完全相同的。这是@ConcernedOfTunbridgeWells在评论中讨论的代码生成器系统。
此外,您当前的审计结构使得很难找到可能受同一操作影响的其他记录。当您需要查看受恶意更新或恶意用户删除的所有记录影响的所有记录时,这一点至关重要。我们的审计系统为每个表都有两个表。一个记录操作的日期和时间,另一个表包含有关该操作中更改的所有记录的数据。由于您可能会在一次更新中更改数百万条记录,因此能够查看其他受影响的内容至关重要。
现在回答你的问题,是的,你可以在触发器外访问插入和删除的伪节点。您可以使用OUTPUT子句执行此操作。但是,在您的特定情况下,我认为这是一个非常糟糕的想法。如上所述。
答案 1 :(得分:0)
你的实际问题的答案是否定的。无法将INSERTED
和DELETED
个对象作为参数传递给存储过程。最好的情况是,您可以将它们保存的数据存储到XML
类型的变量中并传递给它们,但这样做不会很好。
您最好坚持当前的触发器策略,并将表的名称作为参数传递给CLR方法。你可能有一个入口点方法,为你要审核的每个表声明了装饰SqlTriggerAttribute
吗?如果是这样,那么只需使用您用作属性的Target
属性的相同值作为泛型方法的参数。