【问题标题】:TSQL: Prevent trigger suppressing error but rolling back transactionTSQL:防止触发器抑制错误但回滚事务
【发布时间】:2016-02-17 16:48:15
【问题描述】:

我遇到了一个问题,其中一个触发器的一部分出现故障。此故障导致包装触发器的事务回滚。问题在于它没有向命令的调用点引发错误。它表现得好像事务中没有错误,并且捕获它的唯一方法是应该更改的数据没有。 在我的示例中,交易发生了很多变化。错误发生在触发器中。一切都回滚了,但是,命令的调用者没有看到 SQL 异常。从来没有通知调用者有问题。

有什么方法可以指示 TSQL 抛出异常,使其向命令的调用点报告错误?

    CREATE TABLE Archive (
        aColumn INT NOT NULL
    )


    CREATE TABLE Source (
        aColumn INT NULL
    )
   --
    CREATE TRIGGER Archive_Trigger ON Source
    AFTER UPDATE
    AS
    BEGIN
        INSERT INTO Archive
        SELECT DELETED.aColumn
        FROM DELETED
    END

   -- Other attempts
    CREATE TRIGGER Archive_Trigger ON Source
    AFTER UPDATE
    AS
    BEGIN
        BEGIN TRY
            INSERT INTO Archive
            SELECT DELETED.aColumn
            FROM DELETED
       END TRY
       BEGIN CATCH
            SET XACT_ABORT ON;
            DECLARE @ErrorMessage NVARCHAR(4000) ;
            SET @ErrorMessage = ERROR_MESSAGE() ;
            RAISERROR('Error %s occurred in Archive Trigger', 16, 1, @ErrorMessage) ;
       END CATCH
    END

这是触发器所做工作的一个非常幼稚的版本。问题没有发生在生产系统上。当它发生时是在开发环境中。当 /Source/ 表更改时,/Archive/ 表也必须更改。有时会忘记存档,这就是发生错误的时候。例如,如果 aColumn 在存档中不为空,在源中为空。这个实例会导致问题。

如果我执行以下代码,则在表中有一行具有空 aColumn 之后。我期望的结果是得到一个异常。我不。

DataSource source;
Connection connection = source.getConnection();
PreparedStatement statement = connection.prepareStatement("UPDATE Source SET aColumn = NULL");
statement.executeUpdate();

【问题讨论】:

  • 能否添加一些示例代码
  • @TheGameiswar 我已经添加了一些代码,但我不知道它会有多大帮助。该问题的代码方式并不多。
  • 您可以通过显式命名插入中的列来消除错误。无论如何,这应该作为一种通用方式来避免这种确切的情况。当然,如果删除一列,这仍然是一个问题。
  • 我们不能使用isnull来纠正错误
  • 这将修复这种情况下的错误。问题不是特定的错误。问题是表格中的偏差。 /Source/ 被更改并且 /Archive/ 被遗忘。这会导致错误回滚但不会引发错误。

标签: sql sql-server hibernate tsql


【解决方案1】:

一些异常有severity 不足以中断当前批处理的执行。它只是继续执行后续语句。如果我没记错的话,例如,违反约束不是批处理错误。

为了确保整个批次在出现明显错误时中断,您可以将XACT_ABORT 选项设置为ON,如果已建立,这也将回滚整个事务

管理异常的最佳方法是用 TRY-CATCH 块(supposed)包围您的代码,以对严重程度高于信息消息且低于中断连接错误的所有错误作出反应。在陷入 CATCH 块之后(除非您有意使异常保持沉默,否则这是无法避免的),您可能会抛出另一个用户定义的异常,回滚等,正如您自己所知道的那样。 TRY-CATCH 保证您的代码将对异常做出反应(如果有)。

不推荐在TRY-CATCH 中启用XACT_ABORT,因为这有点无意义:您正试图控制 代码在异常情况下的行为(使用try-catch)并且在同时告诉服务器“abort'em all!”(使用 xact_abort)。

【讨论】:

  • 我已经更新了我的示例,以便对其进行尝试。即使使用 try catch 并将错误级别提高到 16,我认为应该足够高,调用点仍然不会收到触发器中的错误通知。 msdn.microsoft.com/en-us/library/ms164086.aspx
  • Try-catch 应该在 DML 语句周围的触发器之外。并且在 catch 块中不需要 xact_abort。
【解决方案2】:

RAISERROR 语句不是停止处理的严重错误。其实MSDN says

THROW 语句支持 SET XACT_ABORT。 RAISERROR 没有。新的 应用程序应该使用 THROW 而不是 RAISERROR。

您可以通过运行以下 SQL 来验证此行为:

set xact_abort on
begin tran
raiserror('error 1', 16, 1, null)
raiserror('error 2', 16, 1, null)

输出将是:

Msg 50000, Level 16, State 1, Line 3
error 1
Msg 50000, Level 16, State 1, Line 4
error 2

除非您是系统管理员,否则任何引发严重错误的尝试都会失败:

消息 2754,级别 16,状态 1,第 8 行错误严重级别大于 18 只能由 sysadmin 角色的成员指定,使用 WITH LOG 选项。

我确实认为您应该使用 SET XACT_ABORT ON,但是:

1) 在实际的INSERT 期间执行SET XACT_ABORT ON。这将导致原始错误终止连接。

例如:

CREATE TRIGGER Archive_Trigger ON Source
AFTER UPDATE
AS
BEGIN
    SET XACT_ABORT ON
    INSERT INTO Archive
    SELECT DELETED.aColumn
    FROM DELETED
END

2) 或者,您可能会在BEGIN CATCH 语句中导致更严重的错误。例如,使用 SELECT 1/0 导致除以 0 错误。客户端必须忽略除以零错误才能获得您提出的错误消息。

注意the original SET XACT_ABORT setting will be restored after leaving the trigger(搜索“触发器”)。

当触发器确实导致取消触发器的错误时,SQL Server 将向客户端引发错误 3609,并带有以下文本:

事务在触发器中结束。该批次已中止。

我们的应用程序中发生了这个特定问题。事务在触发器内被终止。客户端应用程序没有收到错误并在事务之外继续前进。此时发生了非常糟糕的事情。

我们通过让数据访问对象 (DAO) 检测当前数据库连接何时关闭、中断或事务为空来解决此问题。在这种情况下,如果在 SqlException.Errors 中返回 SQL 错误,编号为 3609,则 DAO 会引发特定的“事务在触发器中结束”异常。

【讨论】:

    【解决方案3】:

    如果我对您的理解正确,您的开发人员正在创建触发器但插入到错误的存档表中,因为他们忘记更改脚本?

    我们所做的是使用动态 sql 创建所有审计触发器。我们的设计对每个审计表都有相同的列(基本上是一个 rowid,它是表的 id 列,一个列名列和一个旧值列和一个新值列。所以我们动态发送的是我们正在创建的表是基于将要触发的表名的变量放在动态 sql 中使用的变量中。如果要使审计表与表的列匹配,则要复杂一些,然后需要从系统表中获取列名并放入一个临时表并加入到您的动态 sql 中。

    当需要创建审计表和触发器时,我们运行一个脚本来完成这两项工作,我们所要做的就是输入要放置审计表的数据库和表的名称。

    这需要一段时间才能正确解决,但一旦你这样做了,创建新的审计是轻而易举的事,而且你永远不会出错,比如引用错误的审计表。

    【讨论】:

    • 我很喜欢这个主意。但是,它并没有回答这个问题。
    猜你喜欢
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    • 2010-10-27
    • 2012-01-09
    • 2014-07-17
    相关资源
    最近更新 更多