我遇到一个问题,其中一个触发器的一部分失败了。此故障导致包装触发器的事务回滚。问题在于它没有向命令的调用点引发错误。它的行为就像交易中没有错误一样,它被捕获的唯一方法是应该更改的数据不是。 在我的例子中,发生的事情是交易中有很多变化。触发器中发生错误。然而,一切都被回滚,命令的调用者没有看到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 / table也必须更改。有时会忘记存档,也就是错误发生的时间。例如,如果aColumn在归档中不为null而在Source中为null。这个实例会导致问题。
如果我执行以下代码,在表中有一行null aColumn。我期望的结果是得到一个例外。我没有。
DataSource source;
Connection connection = source.getConnection();
PreparedStatement statement = connection.prepareStatement("UPDATE Source SET aColumn = NULL");
statement.executeUpdate();
答案 0 :(得分:1)
有些例外severity不足以打破当前批次的执行。它只是继续并执行后续声明。如果我没有弄错,例如,约束违规不是批处理错误。
为了确保明显错误的整批中断,您可以将XACT_ABORT选项设置为ON
,如果已建立,它还将回滚整个事务。
管理异常的最佳方法是使用TRY-CATCH块supposed包围您的代码,以响应严重性高于信息消息且低于错误断开连接的所有错误。落入CATCH块后(除非你故意使异常静音,否则无法避免)你可能会抛出另一个用户定义的异常,回滚等等,正如你自己所知道的那样。 TRY-CATCH保证您的代码会对异常做出反应(如果有的话)。
建议不要在XACT_ABORT
内启用TRY-CATCH
,因为它有点无意义:如果发生异常,您正试图控制代码的行为(使用try-catch)并同时告诉服务器“abort'em all!”(使用xact_abort)。
答案 1 :(得分:0)
如果我理解正确,你的开发人员正在创建触发器,但因为忘记更改脚本而插入错误的存档表?
我们所做的是使用动态sql创建所有审计触发器。我们的设计为每个审计表都有相同的列(基本上是一个rowid,它是表的id列,列名列和旧值列以及新值列。所以我们动态发送的是名称我们正在创建的表,它基于将您想要触发的表名的变量放入动态sql中使用的变量。如果要使审计表与表的列匹配,那么它会更复杂一点,那么你需要从系统表中获取列名并放入临时表并加入动态sql中的列名。
当需要创建审计表和触发器时,我们运行一个执行这两个操作的脚本,我们所要做的就是输入我们放置审计表的数据库和表的名称。
需要一段时间才能解决这个问题,但是一旦你做到了,创建新的审计就很容易,你就不会像引用错误的审计表那样出错。
答案 2 :(得分:0)
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)在实际SET XACT_ABORT ON
期间执行INSERT
。这将导致原始错误终止连接。
例如:
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)检测当前数据库连接何时关闭,中断或事务为空来解决这个问题。在这种情况下,如果在具有3609的SqlException.Errors中返回SQL错误,则DAO会抛出特定的"该事务在触发器中结束"异常。