触发器检测存储过程是否调用了DELETE或UPDATE

Trigger to detect whether DELETE or UPDATE is called by stored proc

我有一个场景,某些用户必须有权更新或删除生产中的某些记录。我想做的是采取保护措施,以确保他们不会意外更新或删除 table 的大部分(或全部),而是根据他们的需要只更新或删除几条记录。所以我写了一个简单的触发器来完成这个。

CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS  
BEGIN 
    IF (SELECT COUNT(*) FROM Deleted) > 15
    BEGIN
        RAISERROR ('Bulk deletes from this table are not allowed', 16, 1)
        ROLLBACK
    END  
END --end trigger 

但问题来了。有一个存储过程可以对 table 进行批量更新。用户可以而且应该被允许调用存储过程,因为它的范围更受限制。所以不幸的是,我的触发器会阻止他们在需要时调用存储过程。

我想到的唯一解决方案是 运行 将存储过程作为模拟用户,然后修改触发器以将该用户排除在逻辑之外。但这会在我的环境中带来其他问题。不是无法克服table,而是很烦人。尽管如此,这似乎是唯一可行的选择。

我的思考方式是否正确,或者是否有更好的方法?

一种方法是:

  • 创建一个存储过程来执行所需的工作
  • 如果标准差异很大,请为每种“类型”的工作创建多个程序
  • 向所需用户授予执行允许他们执行的那些过程(“工作种类”)的权限。 (您正在使用数据库角色和域组,对吗?)
  • 撤消对 table
  • 进行临时数据修改的权限

设置起来比较繁琐,但支持“最少权限原则”,允许用户只做他们应该做的事情。

您可以在触发器中添加 @@NESTLEVEL 的检查。临时语句的值为 1,从存储过程调用时为 2。

CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS  
BEGIN 
    IF (SELECT COUNT(*) FROM Deleted) > 15
        AND @@NESTLEVEL = 1 --ad-hoc delete
    BEGIN
        RAISERROR ('Bulk deletes from this table are not allowed', 16, 1);
        ROLLBACK;
    END;  
END;

我通常用 CONTEXT_INFO() 来处理这个问题。这为您提供了比 @@NESTLEVEL 更好的控制,因为您实际上可以识别执行调用的特定存储过程,并在需要时单独处理它们。你这样做如下:

  1. 将过程名称添加到 CONTEXT_INFO() 例如
  -- START OF STORED PROCEDURE
  -- Tell the trigger who we are, and that we can be trusted.
  declare @OldContext char(128), @NewContext varbinary(128);

  -- Get existing context_info()
  set @OldContext = coalesce(convert(char(128), context_info()), '');
  -- Add new info to context_info
  set @NewContext = convert(varbinary(128),convert(char(128), 'dbo.MyProcedureName'));
  -- Store new context info
  set context_info @NewContext;


  -- STORED PROCEDURE CONTENT


  -- END OF STORED PROCEDURE
  -- Restore context_info
  set @NewContext = convert(varbinary(128), @OldContext);
  set context_info @NewContext;
  1. 如果 CONTEXT_INFO() 来自可信来源,则尽早触发 return,例如
-- START OF TRIGGER
declare @NewContext char(128) = coalesce(convert(char(128),context_info()),'');

if @NewContext in ('dbo.MyProcedureName') begin
  return;
end;

这种方法的另一个优点(对于其他触发器用途)是您可以避免在从 SP 调用时在触发器中执行逻辑。因为您经常将逻辑放在触发器中,以确保无论 insert/update/delete 如何发生,它都会发生。但是当在 SP 中完成时,您可以确保在 SP 中执行所需的逻辑,从而避免需要在触发器中执行。如果由于触发器中的逻辑太多而导致性能问题,这尤其有用。

注意:对于 SQL Server 2016+,您可以以类似的方式使用 SESSION_CONTEXT()