【问题标题】:Trigger to detect whether DELETE or UPDATE is called by stored proc触发检测存储过程是否调用了 DELETE 或 UPDATE
【发布时间】:2021-09-25 11:16:52
【问题描述】:

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

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 

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

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

我的想法是正确的,还是有更好的方法?

【问题讨论】:

  • 也许保存一个SESSION_CONTEXT 然后在触发器中读回它。顺便说一句,你不应该在触发器中RAISERROR/ROLLBACK,而只是THROW,回滚会自动发生

标签: sql sql-server tsql triggers


【解决方案1】:

这样做的一种方法是:

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

设置起来可能很麻烦,但它支持“最少权限原则”,允许用户只做他们应该做的事情。

【讨论】:

  • 是的,我对所有事情都使用角色,因此用户可以适当地分组到他们可以做什么和不能做什么。但是,可以执行的临时任务的数量足够大,以至于为每种“类型”的工作创建一个存储过程会很麻烦。但这将是另一种合理的方法。
【解决方案2】:

您可以在触发器中添加对@@NESTLEVEL 的检查。对于 ad-hoc 语句,该值将为 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;

【讨论】:

    【解决方案3】:

    我通常使用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() 来自受信任的来源(例如),则在触发器中提前返回
    -- START OF TRIGGER
    declare @NewContext char(128) = coalesce(convert(char(128),context_info()),'');
    
    if @NewContext in ('dbo.MyProcedureName') begin
      return;
    end;
    

    这种方法的另一个优点(用于其他触发器用途)是您可以避免在从 SP 调用时在触发器中执行逻辑。因为通常您将逻辑放在触发器中以确保无论插入/更新/删除如何发生,它都会发生。但是当在 SP 中完成时,您可以确保在 SP 中执行所需的逻辑,从而避免在触发器中执行此操作。如果由于触发器中的逻辑过多而最终导致性能问题,则特别有用。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-03
      • 1970-01-01
      • 2018-01-18
      • 1970-01-01
      • 1970-01-01
      • 2010-10-30
      • 1970-01-01
      • 2021-11-02
      相关资源
      最近更新 更多