【问题标题】:Is there a way to disable a SQL Server trigger for just a particular scope of execution?有没有办法为特定的执行范围禁用 SQL Server 触发器?
【发布时间】:2025-12-20 08:45:07
【问题描述】:

在 SQL Server 2005 中,触发器是否有办法找出负责触发触发器的对象?我想用它来禁用一个存储过程的触发器。

有没有其他方法可以只为当前事务禁用触发器?我可以使用下面的代码,但如果我没记错的话,它也会影响并发事务——这将是一件坏事。

DISABLE TRIGGER { [ schema_name . ] trigger_name [ ,...n ] | ALL } ON { object_name | DATABASE | ALL SERVER } [ ; ]

ENABLE TRIGGER { [ schema_name . ] trigger_name [ ,...n ] | ALL } ON { object_name | DATABASE | ALL SERVER } [ ; ]

如果可能,我想避免在我的表中使用“NoTrigger”字段并执行NoTrigger = null 的技术,因为我希望表尽可能小。

我想避免触发器的原因是因为它包含对手动更新表很重要的逻辑,但我的存储过程会处理这个逻辑。因为这将是一个高度使用的过程,所以我希望它快。

触发器会在服务器上施加额外的开销,因为它们会启动隐式事务。一旦执行了触发器,就会启动一个新的隐式事务,并且事务内的任何数据检索都将锁定受影响的表。

发件人:http://searchsqlserver.techtarget.com/tip/1,289483,sid87_gci1170220,00.html#trigger

【问题讨论】:

  • 您可以使用 Exec 函数来禁用和启用存储过程中的触发器。示例:`EXEC ('ENABLE TRIGGER dbo.TriggerName on dbo.TriggeredTable')`

标签: sql-server tsql triggers


【解决方案1】:

我刚刚在 SQL Server Central 时事通讯上看到了这篇文章,它似乎提供了一种在连接上使用 Context_Info 可能对您有用的方法:

http://www.mssqltips.com/tip.asp?tip=1591


由 Terrapin 编辑:

以上链接包含以下代码:

USE AdventureWorks;  
GO  
-- creating the table in AdventureWorks database  
IF OBJECT_ID('dbo.Table1') IS NOT NULL  
DROP TABLE dbo.Table1  
GO  
CREATE TABLE dbo.Table1(ID INT)  
GO   
-- Creating a trigger  
CREATE TRIGGER TR_Test ON dbo.Table1 FOR INSERT,UPDATE,DELETE  
AS  
DECLARE @Cinfo VARBINARY(128)  
SELECT @Cinfo = Context_Info()  
IF @Cinfo = 0x55555  
RETURN  
PRINT 'Trigger Executed'  
-- Actual code goes here  
-- For simplicity, I did not include any code  
GO  

如果您想阻止触发器被执行,您可以执行以下操作:

SET Context_Info 0x55555 
INSERT dbo.Table1 VALUES(100)

【讨论】:

  • 我真的希望有更好的方法来处理会话(?)变量/状态:如果Context_Info 已经用于其他目的怎么办? (例如实现多租户。):(
  • @TheMouthofaCow 我没有关注你。您的评论是正确的,但我不明白为什么它与 OP 的用例有关。诚然,我根本不喜欢禁用触发器的整个想法(即使有上下文检查),但我不确定你在这里说什么是错误的。
  • @TheMouthofaCow 值得庆幸的是,我们可以提出比标题允许的更多上下文的问题。您是否通过阅读标题并忽略问题的实际内容来回答所有问题?
  • @TheMouthofaCow 答案解决了 OP 遇到的问题。答案不能解决您的问题是无关紧要的。也就是说,您的投票(赞成或反对)由您自行决定,无论是本着 SE 网络的精神还是其他方式。
  • @TheMouthofaCow 我们是否可以将标题更改为“有没有办法为特定的执行范围禁用 SQL Server 触发器?”这将处理一切。
【解决方案2】:

如果您的触发器导致应用程序出现性能问题,那么最好的方法是删除对表的所有手动更新,并要求所有更新都通过包含正确更新逻辑的插入/更新存储过程。然后您可以完全移除触发器。

如果没有其他方法,我建议拒绝表更新权限。

这也解决了重复代码的问题。在更新 SP 和触发器中复制代码违反了良好的软件工程原则,并且将成为维护问题。

【讨论】:

  • 强烈反对。可以通过多种方式更改数据,并且必须在数据库级别强制执行业务规则。如果触发器表现不佳,它应该被重写但永远不会被删除或禁用。您的方法可能会造成非常糟糕的数据完整性问题。
  • 存储过程已经维护了数据完整性,并且可以限制对表的访问,以便任何希望更新它的进程都必须使用 proc。如果您允许以多种方式访问​​表,则触发器只是问题的症状,而不是解决问题的方法。
  • 如果可以按照 Jeffrey 的建议去做,那将是最好的解决方案。
  • 虽然我 100% 同意您对我构建的任何东西的看法,但其他人(比如我自己)却被困在与马虎的第 3 方应用程序及其加载了触发器的后端数据库作斗争。每天晚上我们都有更新数据的作业,但在触发所有触发器时这样做太慢了,或者某些触发器会导致更新的记录通过接口返回 - 当我们不需要进行编辑时。跨度>
【解决方案3】:

ALTER TABLE tbl 禁用触发器 trg

http://doc.ddart.net/mssql/sql70/aa-az_5.htm

我不明白你第一段的意思

【讨论】:

  • 我的第一段 - 在我的触发器中,是否有一个函数可以用来获取触发我的触发器的存储过程的名称?有点像使用if (trigger_nestlevel(object_id('NameOfTrigger')) = 0) 来确保触发器不会调用自己
【解决方案4】:

既然您指出触发器包含处理所有更新(甚至是手动更新)的逻辑,那么这应该是逻辑所在的位置。您提到的示例,其中存储过程“将处理此逻辑”意味着重复代码。此外,如果您想确保无论作者如何,每个 UPDATE 语句都应用了这个逻辑,那么触发器就是它的位置。当有人编写程序但又忘记复制逻辑时会发生什么?当需要修改逻辑时会发生什么?

【讨论】:

    【解决方案5】:

    不确定这是否是个好主意,但它似乎对我有用。事务应防止触发器被禁用时从其他进程插入到表中。

    IF OBJECT_ID('dbo.TriggerTest') IS NOT NULL
     DROP PROCEDURE dbo.TriggerTest
    GO
    
    CREATE PROCEDURE [dbo].[TriggerTest]
    AS
    BEGIN TRANSACTION trnInsertTable1s
    ;
    DISABLE TRIGGER trg_tblTable1_IU ON tblTable1
    ;
    BEGIN -- Procedure Code
        PRINT '@@trancount'
        PRINT @@TRANCOUNT
        -- Do Stuff
    
    END -- Procedure Code
    ;
    ENABLE TRIGGER trg_tblTable1_IU ON tblTable1
    
    IF @@ERROR <> 0 ROLLBACK TRANSACTION
    ELSE COMMIT TRANSACTION
    

    【讨论】:

    • 这是一种创建或更新现有存储过程的奇怪方式(有一个未经批准的编辑将其更改为简单的删除/创建,并在删除过程中修复了您的块复制错误)。这样做有什么好处吗?谢谢!
    • 这只是用作添加/更新。如果您放弃该过程,您可以确定“CREATE PROCEDURE”将起作用。我在源代码管理中存储存储过程时使用过它。每个 .sql 文件都是一个脚本,如果存在,则删除当前存储过程。它与答案无关。 If/Else 也可以使用,但存储过程的内容需要复制。
    • 当然,我只是因为编辑才注意到它。是的,我们通常有“如果过程存在 drop, create”——我只是没见过“drop, create as 'select 1', update”。
    • 感谢您的提问。我有一段时间没有考虑过。那是样板代码。它添加了 create as select 1 并且还向标准帐户添加了执行权限。然后下面的代码做了一个改变并拥有实际的代码。使用实际帐户编辑样板文件,每个存储过程的开头都相同,除了名称之外,它们都具有标准帐户的执行权限。在某些情况下很有用。希望这会有所帮助。
    【解决方案6】:

    不要禁用触发器。您是正确的,这将禁用任何并发事务。

    为什么要禁用触发器?它有什么作用?为什么触发器会导致问题?从数据完整性的角度来看,禁用触发器通常是个坏主意。

    【讨论】:

      【解决方案7】:

      如果性能是问题,请考虑重写触发器以提高性能。

      【讨论】:

        【解决方案8】:

        我在这个问题上有点胡扯。一方面,我非常反触发,主要是因为除了问题帖子中链接的文章中所述的原因之外,我还有一个地方可以查找针对我的表执行的代码。

        另一方面,如果您有执行稳定且不可变的业务规则或跨表操作的逻辑(例如维护历史表),那么将其放入触发器中会更安全,这样程序作者和程序员就不会需要处理它 - 它只是工作。

        所以,我的建议是将必要的逻辑放在你的触发器中,而不是放在一个 proc 中,因为它不可避免地会增长到具有相同豁免的多个 proc。

        【讨论】:

          【解决方案9】:

          我刚遇到同样的问题,想出了以下解决方案,对我有用。

          1. 创建一个永久数据库表,其中包含您要禁用的每个触发器的一条记录(例如 refTriggerManager);每行包含触发器名称(例如 strTriggerName = 'myTrigger')和一个位标志(例如 blnDisabled,默认为 0)。

          2. 在触发器主体的开头,在 refTriggerManager 中查找 strTriggerName = 'myTrigger'。如果 blnDisabled = 1,则返回而不执行其余的触发代码,否则继续触发代码直到完成。

          3. 在要禁用触发器的存储过程中,执行以下操作:


          开始交易

          更新 refTriggerManager SET blnDisabled = 1 WHERE strTriggerName = 'myTrigger'

          /* 更新拥有“myTrigger”但您希望禁用的表。由于 refTriggerManager.blnDisabled = 1,'myTrigger' 在不执行其代码的情况下返回。 */

          更新 refTriggerManager SET blnDisabled= 0 WHERE triggerName = 'myTrigger'

          /* 触发触发器的可选最终更新代码。由于 refTriggerManager.blnDisabled = 0,'myTrigger' 完全执行。 */

          提交交易


          所有这些都发生在一个事务中,因此它与外界隔离,不会影响目标表上的其他 UPDATE。

          有人认为这种方法有什么问题吗?

          比尔

          【讨论】:

          • 这对于READ COMMITTED SNAPSHOT 来说看起来很糟糕,在标准READ COMMITTED 中它会阻塞所有其他 DML 事务直到完成,从而阻止有效使用行级锁。基本上,您已经实现了已接受答案的残缺版本。如果这不是一个老问题,我可能会给你 -1 只是因为可怕的匈牙利符号和没有格式。
          【解决方案10】:

          我同意其他一些答案。不要禁用触发器。

          这是纯粹的观点,但我会避免像瘟疫这样的触发因素。我发现很少有使用触发器来强制执行数据库规则的情况。在我的经验中有明显的边缘案例,我只有我的经验可以做出这样的声明。我通常看到触发器用于插入一些关系数据(应该从业务逻辑完成),用于将数据插入报告表中,即非规范化数据(可以通过事务外部的流程完成),或用于转换数据某种程度上来说。

          触发器有合法的用途,但我认为在日常业务编程中它们很少而且相差甚远。这可能对您当前的问题没有帮助,但您可以考虑完全移除触发器并以其他方式完成触发器正在执行的工作。

          【讨论】:

          • "DDL 语句不在事务上下文中运行?"您可以自己尝试一下,看看这是错误的。顺便说一句,Red Gate 的 SQL Compare 可以生成作为一个事务运行的部署脚本。
          • 你是对的。我想我在甲骨文的日子一定毒害了我。
          【解决方案11】:

          您可以使用 'Exec' 函数来禁用和启用来自存储过程的触发器。示例:EXEC ('ENABLE TRIGGER dbo.TriggerName on dbo.TriggeredTable')

          【讨论】:

            最近更新 更多