【问题标题】:Commit and Raiserror in a trigger在触发器中提交和引发错误
【发布时间】:2011-03-17 17:23:13
【问题描述】:

也许有更好的方法来做到这一点。我想要的要点是让 SQL Server 向我提出 2 种类型的错误:更新表时来自触发器的警告和错误。如果 SQL 服务器返回警告,则触发器应提交但向用户显示警告(使用 .NET - 最好通过仅在严重性 > 10 时引发的 SQL 异常),如果是错误,则触发器应回滚并显示错误给用户(通过 SQL 异常)。我在这方面的尝试(不用说这是行不通的)是有一个这样的触发器:

ALTER TRIGGER [dbo].[TR_TRANSACTION_UPDATE]
ON [dbo].[tTRANSACTION]
FOR UPDATE
AS
BEGIN
   ...

    BEGIN TRY
    DECLARE @id INT ,@maxid INT
    SELECT @id = 0 ,@maxid = MAX(transID) FROM INSERTED 
    WHILE @id < @maxid 
        BEGIN
            SELECT  @id = MIN([TransID]) FROM INSERTED WHERE [TransID] > @id
                EXEC dbo.sp_CheckTransaction @TransID = @id
            END
        COMMIT TRANSACTION
    END TRY
    BEGIN CATCH
        DECLARE @err_msg VARCHAR(MAX), @err_sev AS INT, @err_state AS INT
        SELECT @err_msg = ERROR_MESSAGE(), @err_sev = ERROR_SEVERITY(), @err_state = ERROR_STATE()
        IF @err_state <> 120  -- '120 is not a fatal error from STORED_PROC
              ROLLBACK TRANSACTION
        ELSE
              COMMIT TRANSACTION

        RAISERROR(@err_msg, @err_sev, @err_state)
    END CATCH
END

此触发器不起作用,因为它认为事务不可提交。

另外,sp_CheckTransaction 可以引发 2 类错误:

 RAISERROR(@msg, 15, 120)  -- warning
 or RAISERROR(@msg, 15, 121)  -- error

我不能使用小于 15 的严重性的原因是因为我希望在 .NET 中显示警告(我可以根据状态和严重性来决定是警告还是错误)。 .NET SqlException 仅在严重性 > 10 时引发。

【问题讨论】:

    标签: c# .net sql sql-server vb.net


    【解决方案1】:
     RAISERROR(@err_msg, @err_sev, @err_state)
    

    你不能这样做。您不得提出系统消息,只能提出用户消息(错误编号高于 50000)。你必须像这样加注:

    RAISERROR(N'An error occured in trigger: %s %d %d', 
          <severity>, <state>, @err_msg, @err_sev, @err_state);
    

    [更新:其实@err_msg是消息,不是错误号,你的用法没问题,所以忽略这个]

    有关此主题的更详细讨论,请参阅Exception handling and nested transactions。您会看到那里解释了为什么您的实现存在缺陷(它没有按应有的方式检查 XACT_STATE()),并且链接的文章提供了更好的实现。

    现在回到您的痛点:您需要了解 SQL Server 错误严重性模型。严重性高于 10 的错误是错误,ADO.Net 将为它们引发异常。严重性低于 10 的错误是信息性消息,ADO.NEt 将为它们引发SqlConnection.InfoMessage 事件。另见What do the different RAISERROR severity levels mean?

    所以问题的真正根源不是您的触发器,而是sp_CheckTransaction 存储过程。它应该为错误提高严重性 16,为警告提高严重性 0:

    RAISERROR(@msg, 0, 120)  -- warning
    

    RAISERROR(@msg, 16, 121)  -- error
    

    并使用SqlConnection.InfoMessage 拦截警告。尝试使用状态作为严重性将让你一事无成。

    您的触发器可能捕获并重新抛出错误,具体取决于多种因素,但您不太可能需要这样做。如果确实捕获了异常,则需要引发 new 错误,并且需要适当的逻辑来处理严重性(引发 16 表示错误,0 表示警告)。切勿在 T-SQL 代码中使用除 16 和 0 以外的任何其他严重性。

    附带说明,从 SQL Server 的下一版本开始,您将能够使用简单的THROW; 重新抛出原始异常,这与 .Net 异常处理非常相似。更多信息SQL Server v.Next (Denali) : Exploring THROW.

    【讨论】:

      【解决方案2】:

      我会反对这样的设计。它使您更难测试您的代码,但如果您因为某种原因一直坚持这样做,这里是如何做到的。

      raiserror ( 'foo', 1, 1 ) 将引发错误,但不会导致触发器的执行停止。如果严重性大于 10,我相信它会回滚事务。

      http://msdn.microsoft.com/en-us/library/ms177497.aspx

      然后您必须使用 SqlConnection 来显示错误消息。

      【讨论】:

        【解决方案3】:

        你试过了吗:

        IF @err_state <> 120  -- '120 is not a fatal error from STORED_PROC'
        

        而不是

        IF @err_state <> 120  -- '120 is not a fatal error from STORED_PROC 
        

        【讨论】:

          猜你喜欢
          • 2022-06-27
          • 1970-01-01
          • 1970-01-01
          • 2021-08-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-18
          相关资源
          最近更新 更多