admin 管理员组

文章数量: 1087139


2024年3月19日发(作者:matlab语言基础)

嵌套事务的回滚与提交

一、嵌套事务和事务保存点的错误处理

1、对于嵌套事务。

1.外部起事务,内部起事务,内外都有Try Catch

内部出错:如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不

存在,但是之后的操作继续执行。

外部出错:如果外部事物出错,内部和外部事物全部回滚,外部回滚之前的操作全部不

存在,但是之后的操作继续执行。

注:如果内部的事务不起事务名称,内部如果出错,将会回滚掉会话中的全部事务,而

且报异常。

2.外部起事务,内部起事务,内部没有Try Catch

内部出错:如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不

存在,但是之后的操作继续执行。

外部出错:如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不

存在,但是之后的操作继续执行。

3.外部起事务,内部不起事务,但有Try Catch。

内部出错:外部事物正常提交,外部事物不会进入ROLLBACK,内部出错之后的记录也

会正常执行。内部操作中,Try部分在错误出现之前的操作正常,Try部分在操作之后的操

作不执行,然后进入Catch块中执行操作。

外部出错:内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操

作继续执行。

4.外部起事务,内部不起事务,但没有Try Catch.

内部出错:如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不

存在,但是之后的操作继续执行。

外部出错:如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不

存在,但是之后的操作继续执行。

5.外部不起事务,内部起事务,但有Try Catch.

内部出错:外部操作被正常执行,内部ROLLBACK操作前全部回滚,之后的操作正常

执行。

外部出错:出错操作之前的操作不会回滚,出错之后的操作不执行,跳入Catch块中,

内部事务不会回滚。

6.外部不起事务,内部起事务,但没有Try Catch.

内部出错:外部操作被正常执行,内部ROLLBACK操作前全部回滚。由于没有catch

块,所以外部操作全部执行。

外部出错:内部事务正常提交,外部只有当条记录失败,其他操作正常执行,但是有严

重错误报出来。

对于事务保存点

事务保存点只有SAVE和ROLLBACK操作,当外部调用内部保存点,内部出现问题不

影响外部事务,外部操作正常执行。当外部操作出现问题时,内部所有操作都回滚掉。

如:外部起事务,内部起保存点,内外都有Try Catch

内部出错:外部操作正常,不进入Catch,内部事务回滚到保存点,之后的继续执行。

外部出错:如果外部事物在保存点之前出现异常,那么外部和内部所有操作回滚。如果

外部事物在保存点之前出现异常,由于保存点已经提交了事务,导致外部rollback找不到

对应的事务点。

二、事务的嵌套

PRINT 'Trancount before transaction: ' + CAST(@@TRANCOUNT as char(1))

BEGIN TRAN

PRINT 'After first BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))

BEGIN TRAN

PRINT 'After second BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))

COMMIT TRAN

PRINT 'After first COMMIT TRAN: ' + CAST(@@TRANCOUNT as char(1))

COMMIT TRAN

PRINT 'After second COMMIT TRAN: ' + CAST(@@TRANCOUNT as char(1))

在结果中,可以看到每一个BEGIN TRAN 语句都会使@@TRANCOUNT增加1并且

每一个COMMIT TRAN语句都会使其减少1。如前所述,一个值为0的@@TRANCOUNT

意味着没有打开的事务。因此,在@@TRANCOUNT值从1降到0时结束的事务发生在

外层事务提交的时候。因此,每一个内部事务都需要提交。由于事务起始于第一个BEGIN

TRAN并结束于最后一个COMMIT TRAN,因此最外层的事务决定了是否完全提交内部的

事务。如果最外层的事务没有被提交,其中嵌套的事务也不会被提交。

键入并执行以下批来检验事务回滚时所发生的情况:

BEGIN TRAN

PRINT 'After 1st BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))

BEGIN TRAN

PRINT 'After 2nd BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))

BEGIN TRAN

PRINT 'After 3rd BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))

UPDATE Data1

SET value1 = 1000000

WHERE Id = 1

COMMIT TRAN

PRINT 'After first COMMIT TRAN: ' + CAST(@@TRANCOUNT as char(1))

ROLLBACK TRAN

PRINT 'After ROLLBACK TRAN: ' + CAST(@@TRANCOUNT as char(1))

SELECT * FROM Data1

WHERE Id = 1;

在这个示例中,数据表Data1在一个嵌套事务中被更新,这会被立即提交。然后

ROLLBACK TRAN被执行。ROLLBACK TRAN将@@TRANCOUNT减为0并回滚整个事

务及其中嵌套的事务,无论它们是否已经被提交。因此,嵌套事务中所做的更新被回滚,

数据没有任何改变。

始终牢记,在嵌套的事务中,只有最外层的事务决定着是否提交内部事务。每一个

COMMIT TRAN语句总是应用于最后一个执行的BEGIN TRAN。因此,对于每一个

COMMIT TRAN,必须调用一个COMMIT TRAN来提交事务。ROLLBACK TRAN语句总

是属于最外层的事务,并且因此总是回滚整个事务而不论其中打开了多少嵌套事务。正因

为此,管理嵌套事务很复杂。如果每一个嵌套存储过程都在自身中开始一个事务,那么嵌

套事务大部分会发生在嵌套存储过程中。要避免嵌套事务,可以在过程开始处检查

@@TRANCOUNT的值,以此来确定是否需要开始一个事务。如果@@TRANCOUNT大

于0,因为过程已经处于一个事务中并且调用实例可以在错误发生时回滚事务。

三、存储过程和触发器中回滚

如果 @@TRANCOUNT 的值在存储过程完成时与过程执行时不同,则会生成一个

266 信息类错误。该错误不是由触发器中同一个条件生成的。

当调用存储过程时,如果 @@TRANCOUNT 为 1 或更大,并且该过程执行

ROLLBACK TRANSACTION 或 ROLLBACK WORK 语句,则会产生 266 号错误。这是

因为 ROLLBACK 回滚所有未完成的事务,并将 @@TRANCOUNT 减到 0,该值比调用

过程时要小。

如果在触发器中发出 ROLLBACK TRANSACTION:

对当前事务中的那一点所做的所有数据修改都将回滚,包括触发器所做的修改。

触发器继续执行 ROLLBACK 语句之后的所有其余语句。如果这些语句中的任意语句

修改数据,则不回滚这些修改。执行其余的语句不会激发嵌套触发器。

在批处理中,所有位于激发触发器的语句之后的语句都不被执行。

触发器中的 ROLLBACK 关闭并释放所有在包含激发触发器的语句的批处理中声明和

打开的游标。这其中包括了在激发触发器的批处理所调用的存储过程中声明和打开的游标。

在激发触发器 的批处理之前的批处理中所声明的游标将只是关闭,但是在以下条件下,

STATIC 或 INSENSITIVE 游标不关闭:

CURSOR_CLOSE_ON_COMMIT 设置为OFF。

静态游标要么是同步游标,要么是完全填充的异步游标。

当执行触发器时,触发器的操作总是好像有一个未完成的事务在起作用。如果激发触

发器的语句是在隐性或显式事务中,则肯定会这样。在自动提交模式下,也是 如此。当语

句开始以自动提交模式执行时,如果遇到错误,则会有隐含的 BEGIN TRANSACTION 语

句允许恢复该语句生成的所有修改。该隐含的事务对批处理中的其它语句没有影响,因为

当语句完成时,该事务要么提交,要么回滚。但是,当调用触发器时,该 隐含的事务将仍

然有效。

这意味着,只要触发器中发出 BEGIN TRANSACTION 语句,则实际上就开始了一个

嵌套事务。因为当回滚嵌套事务时,嵌套的 BEGIN TRANSACTION 语句将被忽略,触发

器中发出的 ROLLBACK TRANSACTION 总是回滚过去该触发器本身发出的所有 BEGIN

TRANSACTION 语句。ROLLBACK 回滚到最外部的 BEGIN TRANSACTION。

若要在触发器中进行部分回滚,则即使总是以自动提交模式进行调用,也必须使用

SAVE TRANSACTION 语句。以下的触发器阐明了这一点:

CREATE TRIGGER TestTrig ON TestTab FOR UPDATE AS

SAVE TRANSACTION MyName

INSERT INTO TestAudit

SELECT * FROM inserted

IF (@@error <> 0)

BEGIN

ROLLBACK TRANSACTION MyName

END

这也影响触发器中 BEGIN TRANSACTION 语句后面的COMMIT TRANSACTION

语句。因为 BEGIN TRANSACTION 启动一个嵌套事务,而随后的 COMMIT 语句只应

用于该嵌套事务。如果在 COMMIT 之后执行 ROLLBACK TRANSACTION 语句,那么

ROLLBACK 将一直回滚到最外部的 BEGIN TRANSACTION。以下的触发器阐明了这一

点:

CREATE TRIGGER TestTrig ON TestTab FOR UPDATE AS

BEGIN TRANSACTION

INSERT INTO TrigTarget

SELECT * FROM inserted

COMMIT TRANSACTION

ROLLBACK TRANSACTION

此触发器绝对不会在 TrigTarget 表中插入。BEGIN TRANSACTION 总是启动一个

嵌套事务。COMMIT TRANSACTION 只提交嵌套事务,而下面的 ROLLBACK

TRANSACTION 则一直回滚到最外部的 BEGIN TRANSACTION。

四、嵌套事务

相关SQL语句:

ROLLBACK 可以回滚某个事务保存点(SAVE TRAN TranSave1), 如ROLLBACK

TRAN TranSave1,但是要明白的是,回滚事务保存点并不会使事务数@TranCount减

少,你嵌套了几个事务,它还是有几个事务数.

特别注意,如果在父存储过程创建一个事务Tran1,然后在子存储过程执行ROLLBACK

TRAN后,子存储过程会抛出异常!事务只能在同一个存储过程里面创建、回滚和提交,

不允许分离在不同的存储过程里面。

基 于以上特点,我个人觉得嵌套事务的作用不大,SQL的内部处理其实最终就是处理

一个最外层的事务点。SQL抛出事务相关的异常,并不是代码有何问题,而是 在提醒我们

注意事务的控制。 我们只要采用TRY CATCH 方式捕获相关异常就可以,我们只要确保设

计的事务点能正常回滚或提交就OK了。

解决方法1:

TRY CATCH 捕获相关异常

解决方法2:

如果外部已经有事务了,就不再创建内部事务。我想SQL的事务异常提醒就是为了告

诉你,不能随便一个地方放事务。

IF @TRANCOUNT =0

BEGIN TRAN

TRY CATCH 注意事项:

SQL语句不加try catch,即使出现异常,后续的SQL语句也会执行。但是一旦外部

加了try catch,则会捕获异常,导致后续的SQL语句没有执行。

是否SQL的异常有分致命和普遍的,在没加try catch的情况下,普通的可以继续往

下走,但是致命的就不往下走了。这个和C#编程语言有重大的不同,编程语言一旦出现异

常,后续代码就不再执行!

在处理嵌套事务时,要特别注意,不论如何要确保事务被完整的关闭或被回滚!

回滚比较好控制。无论有多少级事务数,只要ROLLBACK 一次就可以。不过如果是

ROLLBACK TRAN TRANNAME,TranName不是第一级的话,则会出现异常,等于没有

执行ROLLBACK操作。

提交就要特别注意了。BEGIN TRAN 创建事务3个,则必须COMMIT TRAN 提交

事务3次,才能确保事务数被完整提交。可以通过@@TRANCOUNT来查看当前事务数。

一旦存储过程没有完整提交事务,则可能出现事务锁表的情况! 如果创建事务的进程销毁

了,即使有未提交的事务,应该也销毁,算回滚了吧?

BEGIN TRANSAVE TRANCOMMIT TRANROLLBACK TRAN嵌套事务示例:BEGIN

TRAN Tran1BEGIN TRAN Tran2COMMIT TRAN 可以单独指定某个事务名,如Tran1,

Tran2进行提交。其实也没什么效果,即使Tran2提交成功了,只要将外层事务Tran1回

滚,Tran2保存的数据照样被回滚!

ROLLBACK TRAN 不能指定某个事务名进行回滚!只能ROLLBACK TRAN 或者

ROLLBACK TRAN Tran1,也就说只能回滚最外层的事务名。如果执行ROLLBACK TRAN

Tran2,SQL会提示“无法回滚 Tran2。找不到该名称的事务或保存点”,出错的原因就

是因为Tran2不是最外层事务。总结一句话:ROLLBACK 要么就所有事务都回滚,要么

就回滚时异常,一个事务都没回滚!


本文标签: 事务 回滚 语句