SQL 2005中的递归更新触发器问题

时间:2009-07-06 05:54:57

标签: sql-server-2005 tsql recursion triggers

下面是带有注释的代码片段,用于描述问题陈述。我们有一个更新触发器,在内部调用同一个表上的另一个更新触发器,尽管Recursive Trigger Enabled Property Set为false。

想了解其原因,因为这会对我的应用程序造成严重破坏。

/* Drop statements for the table and triggers*/  

IF  EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].   [t_upd_TestTrigger_002]'))
    DROP TRIGGER [dbo].[t_upd_TestTrigger_002]
IF  EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[t_upd_TestTrigger_002]'))
    DROP TRIGGER [dbo].[t_upd_TestTrigger_001]
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestTrigger]') AND type in (N'U'))
    DROP TABLE [dbo].[TestTrigger]


CREATE TABLE [dbo].[TestTrigger] /*Creating a test table*/
(
    [InternalKey] INT  NOT NULL,
    [UserModified] varchar(50) DEFAULT SUSER_SNAME()
) 


/* Please run the snippet below as seperate batch, else you will get 
   an error that 'CREATE TRIGGER' must be the first statement in a 
   query batch.

   CREATING A UPDATE TRIGGER FOR THE TEST TABLE
*/

CREATE TRIGGER [t_upd_TestTrigger_001] ON [dbo].[TestTrigger]
FOR UPDATE
AS
BEGIN
    --This trigger has some business logic which gets executed 
    print 'In Trigger 001 '
END

/* Please run the snippet below as separate batch, else you will 
   get an error that 'CREATE TRIGGER' must be the first statement 
   in a query batch.

   CREATING Another UPDATE TRIGGER FOR THE TEST TABLE 

   This trigger updates the audit fields in the table and it has to be 
   a separate trigger, We cannot combine this with other update triggers - 
   So table TestTrigger will have two FOR UPDATE triggers
*/


CREATE TRIGGER [t_upd_TestTrigger_002] ON [dbo].[TestTrigger]
FOR UPDATE
AS
    print 'bad guy starts'
UPDATE SRC
    SET UserModified = SUSER_SNAME()
    FROM inserted AS INS
    INNER JOIN dbo.[TestTrigger] AS SRC
        ON INS.InternalKey = SRC.InternalKey
        print 'bad guy ends'

/* INSERTING TEST VALUE IN THE TEST TRIGGER TABLE*/

INSERT INTO dbo.[TestTrigger](InternalKey,UserModified)
SELECT 1 ,'Tester1'  UNION ALL
SELECT 2,'Tester2' UNION ALL 
SELECT 3 ,'Tester3'

/* TestTrigger table has 3 records, we will update the InternalKey 
   of first record from 1 to 4.  We would expect following actions
   1) [t_upd_TestTrigger_001] to be executed once
   2) [t_upd_TestTrigger_002] to be executed once
   3) A message that (1 row(s) affected) only once.

   On Execution, i find that [t_upd_TestTrigger_002] internally triggers 
   [t_upd_TestTrigger_001].

   Please note Database level property Recursive Triggers enabled is 
   set to false.
*/

/*UPDATE THE TABLE  SEE THE MESSAGE IN RESULT WINDOW*/
UPDATE dbo.[TestTrigger]
SET InternalKey = 4
WHERE InternalKey = 1

3 个答案:

答案 0 :(得分:6)

“启用递归触发器”不会影响传递触发器。

这意味着如果触发器A以激活触发器B的方式更新表,并且触发器B更新同一个表,以便再次运行触发器A,则SQL Server无法检测和禁止此无限循环。特别是因为触发器B可以更新其他表,并且它们上的触发器可以再次更新原始表 - 这可能会变得像你想的那么复杂。

最终,将达到触发器嵌套级别限制,并且循环停止。

我怀疑你的两个触发器都以某种方式更新了源表。如果触发器自行激活,SQL Server只能检测递归触发器。我想你不是这样的。重组触发器是唯一干净的出路。

作为(hackery)的想法:你可以向表中附加一个字段(数据类型和值无关),该字段由无操作但通过触发器更新。然后更改二阶触发器以更新该字段。将该字段的IF UPDATE()检查添加到您的一阶触发器中。如果已设置字段,则防止现在冗余更新。如果这是有道理的。 ; - )

MSDN: Using Nested Triggers,请参阅“直接递归”和“间接递归”部分。

答案 1 :(得分:0)

如Tomalak所述,您可以使用IF UPDATE(),如果正在更新UserModified,则跳过触发逻辑。

另一种可能性是将UserModified列移动到单独的表中以避免递归。

答案 2 :(得分:0)

如果要完全停止数据库中的这种行为,“要禁用间接递归,请使用sp_configure将嵌套触发器服务器选项设置为0.有关详细信息,请参阅Using Nested Triggers。” 当然,总是需要考虑您实际使用嵌套触发器。