【问题标题】:SQL Server Trigger loopSQL Server 触发器循环
【发布时间】:2010-02-10 14:34:33
【问题描述】:

我想知道是否可以在两个表上添加一个触发器,将数据复制到另一个表。

例如:

  • 我有两个用户表,users_V1 和 users_V2,当使用其中一个 V1 应用程序更新用户时,它也会在 users_V2 中激活触发器来更新它。

  • 如果我想在 V2 表上添加相同的触发器,以便在 V2 中更新用户时更新 V1 中的数据,它会进入无限循环吗?有什么办法可以避免。

【问题讨论】:

  • 不可能合并这两张表?
  • 表中用户数据的唯一键是否会阻止再次添加相同的用户?这应该会导致插入失败并触发终止。

标签: sql-server sql-server-2000 triggers infinite-loop


【解决方案1】:

我不建议在处理过程中明确禁用触发器 - 这可能会导致奇怪的副作用。

在触发器中检测(和防止)循环的最可靠方法是使用CONTEXT_INFO()

例子:

CREATE TRIGGER tr_Table1_Update
ON Table1
FOR UPDATE AS

DECLARE @ctx VARBINARY(128) 
SELECT @ctx = CONTEXT_INFO() 
IF @ctx = 0xFF
    RETURN

SET @ctx = 0xFF

-- Trigger logic goes here

有关更详细的示例,请参阅this link


关于 SQL Server 2000 中 CONTEXT_INFO() 的注意事项:

支持上下文信息,但显然不支持CONTEXT_INFO 函数。你必须改用这个:

SELECT @ctx = context_info
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

【讨论】:

  • @Aaronaught,这适用于连接池吗?例如,一个 Web 应用程序在任何地方都使用相同的连接字符串用户 ID 和密码?
  • @adam:触发器总是会在执行语句的同一个连接上运行(实际上是同一个事务)。连接池不会导致触发器的一部分在一个连接上运行,而另一部分在不同的连接上运行。如果您的应用程序实际上是单独发送原始context_info 语句,然后假设它们稍后会在某个不确定的时间点产生影响,那么是的,您将遇到麻烦,但这不是该功能的预期用途。
【解决方案2】:
  • 要么使用TRIGGER_NESTLEVEL() 来限制触发器递归,要么使用

  • 检查目标表是否需要更新:

    IF (SELECT COUNT(1) 
    FROM users_V1 
    INNER JOIN inserted ON users_V1.ID = inserted.ID
    WHERE users_V1.field1 <> inserted.field1
    OR users_V1.field2 <> inserted.field2) > 0 BEGIN
    
    UPDATE users_V1 SET ...
    

【讨论】:

  • IF TRIGGER_NESTLEVEL() > 1 RETURN ----- 工作正常,谢谢
【解决方案3】:

我遇到了完全相同的问题。我尝试使用 CONTEXT_INFO() 但这是一个会话变量,所以它只在第一次工作!然后下次在会话期间触发触发器时,这将不起作用。因此,我最终使用了一个变量,该变量在每个受影响的触发器中返回 Nest Level 以退出。

示例:

CREATE TRIGGER tr_Table1_Update
ON Table1
FOR UPDATE AS
BEGIN
      --Prevents Second Nested Call
      IF @@NESTLEVEL>1 RETURN 

      --Trigger logic goes here
END

注意:如果要停止所有嵌套调用,请使用 @@NESTLEVEL>0

另外一个注意事项——这篇文章似乎对嵌套调用和递归调用有很多混淆。原始海报指的是嵌套触发器,其中一个触发器会导致另一个触发器触发,这将导致第一个触发器再次触发,依此类推。这是嵌套的,但根据 SQL Server,不是递归的,因为触发器不是直接调用/触发自身。递归不是“一个触发器[正在]调用另一个”的地方。那是嵌套的,但不一定是递归的。您可以使用此处提到的一些设置启用/禁用递归和嵌套来测试这一点:blog post on nesting

【讨论】:

    【解决方案4】:

    我支持这种特殊设计场景的无触发器阵营。话虽如此,但我对您的应用程序的功能以及它为什么这样做的了解有限,以下是我的总体分析:

    在表上使用触发器的优点是能够对表上的所有操作进行操作。就是这样,在这种情况下你的主要好处。但这意味着您的用户可以直接访问表或对表有多个访问点。我倾向于避免这种情况。触发器有它们的位置(我经常使用它们),但它是我最后使用的数据库设计工具之一,因为它们往往不太了解它们的上下文(通常是一种优势)以及在它们确实需要的地方使用时了解不同的上下文和整体用例,它们的好处被削弱了。

    如果两个应用版本都需要触发相同的操作,它们都应该调用相同存储过程。存储过程可以确保完成所有适当的工作,当您的应用不再需要支持 V1 时,可以删除存储过程的那部分。

    在您的客户端代码中调用两个存储过程是一个坏主意,因为这是数据库可以轻松一致地提供数据服务的抽象层,而您的应用程序无需担心。

    我更喜欢使用视图、UDF 或 SP 来控制与基础表的接口。用户永远无法直接访问表。这里的另一点是,您可以在用户甚至不知道的情况下呈现单个“用户”视图或 UDF 合并适当的基础表 - 可能达到甚至不需要任何“同步”的地步,因为新属性位于EAV 系统,如果您需要那种病态灵活性或其他一些仍然可以加入的不同结构 - 比如说 OUTER APPLY UDF 等。

    【讨论】:

    • 我同意有更好的方式来向后兼容(我怀疑这可以只用一个表来完成) - 但是当应用程序使用任何类型的 O 时,表的多个访问路径是不可避免的/R 映射器,这是当今大多数应用程序。使用 SP 作为抽象是很好的,最好还是两者兼有 - 通过 SP 路由应用程序调用,然后让 SP 在执行其工作之前禁用触发器。通过这种方式,您可以更改实施并且您可以避免粗心的业务分析师直接访问数据库,他们需要进行大批量更新。
    • @Aaronaught - 当然。一般直接表访问对我来说将是最终的设计更改。我一般不会通过 SP 强制执行任何数据库一致性——我会强制执行访问和限制功能(没有人在没有限制的情况下对数百万行事实表运行查询)。我不是 ORM 的粉丝,从某种意义上说是一种奇迹般的抽象,我更喜欢成为 ORM。如果我的设计足够简单,可以与自动系统一起工作,那么它们应该足够简单,可以在没有我的情况下进行代码生成。
    • 如果我暗示 ORM 是一颗灵丹妙药,那么对于任何形式的误解,我深表歉意。我的 ORM 方法是将它用于 50-90% 的映射工作, 只是简单的工作,其余的使用自定义代码。对于任何问题,很少有一种万能的解决方案;除了存储过程和访问限制之外,触发器和 ORM 只是构建主要系统的两个更有用的工具。
    【解决方案5】:

    您将不得不在触发器中创建某种环回检测。也许在将记录输入下一个表之前使用“如果存在”语句来查看记录是否存在。听起来确实会按照当前设置的方式进入无限循环。

    【讨论】:

    • 是的,我已经在使用 if exists 作为插入触发器,我想知道更新是否有类似的东西。
    • 您将使用相同的逻辑...如果用户存在更新的字段,则不要再次更新。我会将更新的任何字段添加到“如果存在”查询中的 where 语句中。
    【解决方案6】:

    避免像瘟疫这样的触发器......使用存储过程来添加用户。如果这需要一些设计更改,请进行更改。触发器是邪恶的。

    【讨论】:

    • 我也可以直接在App中添加调用“V1更新功能”和V2“更新功能”。代码,我认为触发器会更好有几个原因:其他开发人员不会忘记直接在代码中添加这两个函数,并且当不再需要 V1 时可以很容易地删除触发器。
    • @HLGEM 一旦有了一些触发器,对模式的更改就是一场灾难。触发器是一种快速修复方法——开发人员应该了解如何使用正确的存储过程。
    • 如果您知道自己在做什么,那么对架构的更改并不是一场灾难(我们有很多触发器并进行了很多架构更改,而且还没有遇到过您所说的这场灾难),而且不仅应用程序访问数据库,并且并非所有任务都可以通过 sp 完成(例如,我不会一次使用一条记录更新 250,000 条记录。
    • 并且触发器不是快速修复(在我看来Sps是快速修复),触发器是维护复杂数据完整性规则的正确方法。只有这样才能确保以任何方式更改数据,规则将得到执行。
    • 同意@HLGEM - 这是一个糟糕的答案,存储过程只能提供对通用功能的抽象,它们实际上不能强制执行规则,并且它们不能在整个记录集(使用 UDTT 除外)。我已经进行了几十次 - 不,数百次 的架构更改,并且从未意外破坏触发器;这就是测试的目的。
    【解决方案7】:

    尝试类似的东西(我没有打扰创建触发器的东西,因为您显然已经知道如何编写该部分):

    update t
    set field1 = i.field1
    field2 = i.field2
    from inserted i
    join table1 t on i.id  = t.id
    where field1 <> i.field1 OR field2 <> i.field2
    

    【讨论】:

      【解决方案8】:

      触发器中的递归,即一个触发器调用另一个触发器,仅限于32 levels

      在每个触发器中,只需检查您要插入的行是否已经存在。

      示例

      CREATE TRIGGER Table1_Synchronize_Update ON [Table1] FOR UPDATE AS
      BEGIN
        UPDATE  Table2
        SET     LastName = i.LastName
                , FirstName = i.FirstName
                ,  ... -- Every relevant field that needs to stay in sync
        FROM    Table2 t2
                INNER JOIN Inserted i ON i.UserID = t2.UserID
        WHERE   i.LastName <> t2.LastName
                OR i.FirstName <> t2.FirstName
                OR ... -- Every relevant field that needs to stay in sync
      END
      
      CREATE TRIGGER Table1_Synchronize_Insert ON [Table1] FOR INSERT AS
      BEGIN
        INSERT INTO Table2
        SELECT i.*
        FROM   Inserted i
               LEFT OUTER JOIN Table2 t2 ON t2.UserID = i.UserID
        WHERE  t2.UserID IS NULL
      END
      
      CREATE TRIGGER Table2_Synchronize_Update ON [Table2] FOR UPDATE AS
      BEGIN
        UPDATE  Table1
        SET     LastName = i.LastName
                , FirstName = i.FirstName
                ,  ... -- Every relevant field that needs to stay in sync
        FROM    Table1 t1
                INNER JOIN Inserted i ON i.UserID = t1.UserID
        WHERE   i.LastName <> t1.LastName
                OR i.FirstName <> t1.FirstName
                OR ... -- Every relevant field that needs to stay in sync
      END
      
      CREATE TRIGGER Table2_Synchronize_Insert ON [Table2] FOR INSERT AS
      BEGIN
        INSERT INTO Table1
        SELECT i.*
        FROM   Inserted i
               LEFT OUTER JOIN Table1 t1 ON t1.UserID = i.UserID
        WHERE  t1.UserID IS NULL
      END
      

      【讨论】:

      • 问题指定这是UPDATE,而不是INSERT。该问题假设将始终存在现有数据。
      • @Aaronaught - 我知道,没关系。答案是有效的。如果你愿意,我会更详细地解释它。
      • 请解释一下“检查您要插入的行是否已经存在”这句话如何应用于UPDATE 操作;这当然不是很明显,一个合理的解释会使这个答案更好。
      • @Aaronaught - 我假设两个表具有相同的结构。如果他们不这样做,SELECT * FROM Inserted 需要调整。 (刚刚注意到更新有误,正在修复它)。
      • 也就是说,您提出的解决方案要干净得多。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-30
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多