Pus*_*ode 11 trigger sql-server transaction
当表上的插入触发器失败时,我试图避免数据丢失。我正在使用以下场景尝试此操作,但我的代码失败了。
当在客户表上发生插入时,我想将相同的数据插入到存档表中。当触发器失败时,我不想回滚整个事务,这会导致客户表上的数据丢失。
即使触发器失败,我也希望将数据插入到Customers表中,并且存储过程应该像往常一样返回Customer_ID。
ALTER TRIGGER [dbo].[Customer_Insert_Trigger_Test]
ON [dbo].[Customers]
AFTER INSERT
AS
BEGIN
BEGIN TRY
begin transaction;
set nocount on;
SAVE TRANSACTION InsertSaveHere;
--Simulating error situation
RAISERROR (N'This is message %s %d.', -- Message text.
11, -- Severity,
1, -- State,
N'number', -- First argument.
5); -- Second argument.
Insert into Archive select * from Inserted;
commit transaction;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION InsertSaveHere;
END CATCH
END
Run Code Online (Sandbox Code Playgroud)
我的问题主要是关于如何避免在客户表上的实际插入以在触发器失败时回滚。如何更改我的代码?
Sol*_*zky 10
问题提到“代码失败”,但没有任何错误消息或具体失败的迹象。包括至少一个,如果不是两个,这些信息总是有助于获得更好的答案。
目前,我看到一些关于触发器和事务的假设似乎是不正确的:您@@TRANCOUNT
通过调用增加 ,BEGIN TRAN;
但只有@@TRANCOUNT
在没有错误并且该COMMIT TRAN;
行在TRY
块内执行时才减少。在出错的情况下,将COMMIT
跳过 并ROLLBACK
出现保存点的a 。但是回滚保存点不会减少@@TRANCOUNT
在这种情况下INSERT
操作结束并且事务仍然处于活动状态。
触发器存在于内部启动的事务中,该事务将其绑定到触发触发器的 DML 操作。这是您能够ROLLBACK
在触发器内调用以取消该 DML 操作的方式。
(2020 年 10 月 21 日添加:不知道我在发布此答案时如何或为什么没有想到此选项)
如果可能的话,如果可以测试导致错误的条件,那么最好在尝试有时会失败的操作之前测试该条件。如果错误从未真正发生,则您无需更改默认行为和/或添加自定义事务处理。在您的情况下(即将新记录添加到存档表),您可以执行以下操作之一:
SET NOCOUNT ON;
BEGIN TRY
INSERT INTO dbo.Archive
SELECT *
FROM inserted ins
WHERE NOT EXISTS (SELECT *
FROM dbo.Archive arc
WHERE arc.[Login] = ins.[Login])
-- assuming [Login] field exists and should be unique
END TRY
BEGIN CATCH
DECLARE @DoNothing INT;
END CATCH;
Run Code Online (Sandbox Code Playgroud)
有些时候,这会不会赶上一切,还有可能是偶尔的违规行为,在这种情况下,你仍然可能需要也即使使用选项2使用选项2.但是,它始终工作,最好还是试图阻止错误,因为必须完成工作才能达到导致错误的条件,而这只是浪费时间、IO、争用等。
考虑到这一点,您应该能够删除BEGIN TRAN;
和COMMIT TRAN;
行以使其正常工作。最终效果是,如果没有错误,INSERT
进入Archive
表将按预期提交,但如果有错误,它将执行ROLLBACK
保存点并继续。
但是,在删除这两个部分后,您仍然会遇到以下错误的棘手情况:
消息 3931,级别 16,状态 1,过程 Customer_Insert_Trigger_Test,行 XXXXX
当前事务无法提交且无法回滚到保存点。回滚整个事务。
这种行为的原因似乎是XACT_ABORT ON
系统调用触发器时的隐式设置。的效果XACT_ABORT ON
是取消大多数错误(编译错误或 from 除外)的事务(和查询批处理RAISERROR
)。补救措施?只需XACT_ABORT OFF
在触发器的开头设置即可。
例如,以下对我有用:
CREATE
--ALTER
TRIGGER [dbo].[Customer_Insert_Trigger_Test]
ON [dbo].[Inserts]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
BEGIN TRY
PRINT '@@TRANCOUNT = ' + CONVERT(VARCHAR(10), @@TRANCOUNT); -- for debug only
SAVE TRANSACTION InsertSaveHere;
-- Simulating error situation
DECLARE @Error INT = 1 / 0; -- runtime error
-- Insert into Archive select * from Inserted;
END TRY
BEGIN CATCH
PRINT 'Entering CATCH block...'; -- for debug only
ROLLBACK TRANSACTION InsertSaveHere;
END CATCH;
END;
Run Code Online (Sandbox Code Playgroud)
请注意,这种方法并不会相对于改变触发器在这个表中预期的行为:1)实际COMMIT
在最外层发生(无论是最初的DML语句或超越,如果一个明确的交易已在这之前启动语句),2)此表上的其他潜在触发器发出 ROLLBACK 以取消操作的能力,以及 3)在此表上的 DML 语句之前启动的显式事务的能力,发出 ROLLBACK 以取消所有更改包括对此表的 DML 操作。
当然,如果能够在此错误的唯一的事情就是INSERT
到Archive
表中,那么你很可能也摆脱了SAVE TRAN
和ROLLBACK TRANSACTION InsertSaveHere;
,只是做在事CATCH
块,以便它不是空的,像DECLARE @Test INT;
可能会奏效。这里的推理是,错误从未真正发生过的单个 DML 语句,因此没有什么可以回滚;-)。
要回答这个问题在标题中说:你应该能够COMMIT
触发内,但我会eXtreeemely,因为它改变了当交易将提交或回滚预期的行为,这可能会影响正常操作谨慎做这样的事此表上的其他触发器(ROLLBACK
如果此触发器首先运行,它们将无法发出取消操作的命令),并且会阻止在此表上的 DML 操作之前启动的显式事务的预期操作。
为此(注意:在继续阅读本段之前,您需要直接阅读上面的段落),您将发出 a COMMIT TRAN;
(因为触发器已存在于事务中),然后执行 a BEGIN TRAN;
。The COMMIT TRAN;
will commit the initial DML operation, the BEGIN TRAN;
will put @@TRANCOUNT
back to 1 so that当触发器执行结束时,你不会收到错误,指出触发器以与@@TRANCOUNT
开始时不同的方式结束。