如何防止数据库触发器递归?

Pur*_*ome 26 sql t-sql sql-server triggers sql-server-2008

我在SQL Server 2008数据库的表上有以下触发器.它正在递归,所以我需要阻止它.

插入或更新记录后,我试图只更新该表上的单个字段.

这是触发器:

ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate] 
   ON  [dbo].[tblMedia]
   BEFORE INSERT, UPDATE
AS 
BEGIN
    SET NOCOUNT ON

    DECLARE @IdMedia INTEGER,
        @NewSubject NVARCHAR(200)   

    SELECT @IdMedia = IdMedia, @NewSubject = Title
    FROM INSERTED

    -- Now update the unique subject field.
    -- NOTE: dbo.CreateUniqueSubject is my own function. 
    --       It just does some string manipulation.
    UPDATE tblMedia
    SET UniqueTitle = dbo.CreateUniqueSubject(@NewSubject) + 
                      CAST((IdMedia) AS VARCHAR(10))
    WHERE tblMedia.IdMedia = @IdMedia
END
Run Code Online (Sandbox Code Playgroud)

任何人都可以告诉我如何防止触发器插入再次触发另一个触发器?

Ans*_*sss 52

不确定它是否与OP的问题相关,但是如果你来这里是为了找出如何防止在触发器中发生递归或相互递归,你可以测试这样:

IF TRIGGER_NESTLEVEL() <= 1/*this update is not coming from some other trigger*/
Run Code Online (Sandbox Code Playgroud)

MSDN 链接

  • 这实际上是最好的答案,也是最直接回答海报问题的答案. (5认同)
  • 正如答案的实际说明那样,如果更新来自某个其他触发器,这也可以防止触发触发器。但这不是递归。当更新来自同一触发器时,将发生递归。在这种情况下,您需要将对象ID传递给函数:/sf/answers/3295205581/ (2认同)

Rod*_*igo 33

我看到三种可能性:

  1. 禁用触发器递归:

    这将阻止触发器触发另一个触发器或再次调用自身.为此,请执行以下命令:

    ALTER DATABASE MyDataBase SET RECURSIVE_TRIGGERS OFF
    GO
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用触发器INSTEAD OF UPDATE,INSERT

    使用INSTEAD OF触发器可以控制任何正在更新/插入的列,甚至可以在调用命令之前进行替换.

  3. 通过阻止使用IF UPDATE来控制触发器

    如果触发器正在调用自身,则测试列将以合理的准确度告诉您.为此,请使用以下IF UPDATE()子句:

    ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate]
       ON  [dbo].[tblMedia]
       FOR INSERT, UPDATE
    AS
    BEGIN
        SET NOCOUNT ON
        DECLARE @IdMedia INTEGER,
            @NewSubject NVARCHAR(200)   
    
        IF UPDATE(UniqueTitle)
          RETURN;
    
        -- What is the new subject being inserted?
        SELECT @IdMedia = IdMedia, @NewSubject = Title
        FROM INSERTED
    
        -- Now update the unique subject field.
        -- NOTE: dbo.CreateUniqueSubject is my own function. 
        --       It just does some string manipulation.
        UPDATE tblMedia
        SET UniqueTitle = dbo.CreateUniqueSubject(@NewSubject) + 
                          CAST((IdMedia) AS VARCHAR(10))
        WHERE tblMedia.IdMedia = @IdMedia
    END
    
    Run Code Online (Sandbox Code Playgroud)


Rem*_*anu 8

ALTER DATABASE <dbname> SET RECURSIVE_TRIGGERS OFF
Run Code Online (Sandbox Code Playgroud)

RECURSIVE_TRIGGERS {ON | 关闭}

ON允许AFTER触发器的递归触发.

OFF仅允许直接递归触发AFTER触发器.要同时禁用AFTER触发器的间接递归,请使用sp_configure将嵌套触发器服务器选项设置为0.

RECURSIVE_TRIGGERS设置为OFF时,仅阻止直接递归.要禁用间接递归,还必须将嵌套触发器服务器选项设置为0.

可以通过检查sys.databases目录视图中的is_recursive_triggers_on列或DATABASEPROPERTYEX函数的IsRecursiveTriggersEnabled属性来确定此选项的状态.

  • 要检查设置:`SELECT is_recursive_triggers_on FROM sys.databases WHERE name = 'YourDatabaseName'` (2认同)
  • 这是一个糟糕的解决方案。您不应该仅仅为了解决眼前的问题而全局禁用功能。它可能会破坏现有的代码,并且可能使未来的编码变得更加困难。 (2认同)

Pur*_*ome 5

我想我明白了:)

当标题被"更新"(读取:插入或更新)时,然后更新唯一主题.当触发器第二次运行时,uniquesubject字段会更新,因此它会停止并离开触发器.

此外,我已经让它处理了更改的MULTIPLE行 - >我总是忘记触发器.

ALTER TRIGGER [dbo].[tblMediaAfterInsert] 
   ON  [dbo].[tblMedia]
   FOR INSERT, UPDATE
AS 
BEGIN
    SET NOCOUNT ON

    -- If the Title is getting inserted OR updated then update the unique subject.
    IF UPDATE(Title) BEGIN
        -- Now update all the unique subject fields that have been inserted or updated.
        UPDATE tblMedia 
        SET UniqueTitle = dbo.CreateUniqueSubject(b.Title) + 
                          CAST((b.IdMedia) AS VARCHAR(10))
        FROM tblMedia a
            INNER JOIN INSERTED b on a.IdMedia = b.IdMedia
    END
END
Run Code Online (Sandbox Code Playgroud)


Col*_*lin 5

TRIGGER_NESTLEVEL可以用于防止特定触发器的递归,但是将触发器的对象ID传递到函数中很重要。否则,当另一个触发器进行插入或更新时,您还可以防止触发该触发器:

   IF TRIGGER_NESTLEVEL(OBJECT_ID('dbo.mytrigger')) > 1
         BEGIN
             PRINT 'mytrigger exiting because TRIGGER_NESTLEVEL > 1 ';
             RETURN;
     END;
Run Code Online (Sandbox Code Playgroud)

MSDN

如果未指定任何参数,则TRIGGER_NESTLEVEL返回调用堆栈上的触发器总数。这包括自身。

参考: 避免递归触发器

  • 将 `TRIGGER_NESTLEVEL(@@PROCID)` 视为更通用的检查 - “[[@@PROCID 返回](https://learn.microsoft.com/en-us/sql/t-sql/functions/procid-transact -sql?view=sql-server-ver15)] 当前 Transact-SQL 模块的对象标识符 (ID)。Transact-SQL 模块可以是存储过程、用户定义函数、_或触发器_。” (5认同)
  • @user2864740使用`@@PROCID`来避免魔术字符串`'dbo.mytrigger'`似乎有优点。这就是您对“通用”一词的理解吗?您随后对 SP 和 UDF 的评论听起来像是您试图防止触发器是 SP 或 UDF 而不是触发器这一不存在的问题。 (3认同)
  • 在这种情况下,“特定”和通用与 @@PROCID 与 OBJECT_ID 和相同触发器的名称*相同*(如果检查不同的触发器,则根本不相关)。如果想要更具体,请将第二个和第三个参数传递给 TRIGGER_NESTLEVEL。 (2认同)

DVK*_*DVK 1

您可以有一个单独的 NULLABLE 列来指示是否设置了 UniqueTitle。

在触发器中将其设置为 true 值,如果“INSERTED”中的值为 true,则让触发器不执行任何操作