1、嵌套事务的回滚与提交一、嵌套事务和事务保存点的错误处理1、对于嵌套事务。1.外部起事务,内部起事务,内外都有Try Catch内部出错 :如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。外部出错 :如果外部事物出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。注 : 如果内部的事务不起事务名称,内部如果出错,将会回滚掉会话中的全部事务,而且报异常。2.外部起事务,内部起事务,内部没有Try Catch内部出错 :如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。外部出
2、错 :如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。3.外部起事务,内部不起事务,但有Try Catch 。内部出错 :外部事物正常提交,外部事物不会进入ROLLBACK, 内部出错之后的记录也会正常执行。内部操作中, Try 部分在错误出现之前的操作正常,Try 部分在操作之后的操作不执行,然后进入 Catch 块中执行操作。外部出错 :内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。14.外部起事务,内部不起事务,但没有Try Catch.内部出错 :如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作
3、全部不存在,但是之后的操作继续执行。外部出错 :如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。5.外部不起事务,内部起事务,但有Try Catch.内部出错 :外部操作被正常执行,内部ROLLBACK操作前全部回滚,之后的操作正常执行。外部出错 :出错操作之前的操作不会回滚,出错之后的操作不执行,跳入Catch 块中,内部事务不会回滚。6.外部不起事务,内部起事务,但没有Try Catch.内部出错 :外部操作被正常执行, 内部 ROLLBACK操作前全部回滚。 由于没有 catch 块,所以外部操作全部执行。外部出错 :内部事务正常提交,外部
4、只有当条记录失败,其他操作正常执行,但是有严重错误报出来。对于事务保存点事务保存点只有SAVE 和 ROLLBACK操作,当外部调用内部保存点,内部出现问题不影响外部事务,外部操作正常执行。当外部操作出现问题时,内部所有操作都回滚掉。如:外部起事务,内部起保存点,内外都有Try Catch内部出错 :外部操作正常,不进入Catch ,内部事务回滚到保存点,之后的继续执行。外部出错 :如果外部事物在保存点之前出现异常,那么外部和内部所有操作回滚。如果外部事物在保存点之前出现异常,由于保存点已经提交了事务,导致外部rollback 找不到对应的事务点。2二、事务的嵌套PRINTTrancount
5、before transaction: + CAST( TRANCOUNTaschar ( 1)BEGINTRANPRINTAfter first BEGIN TRAN: +CAST( TRANCOUNTaschar ( 1)BEGINTRANPRINTAfter second BEGIN TRAN: +CAST( TRANCOUNTaschar( 1 )COMMITTRANPRINTAfter first COMMIT TRAN: +CAST( TRANCOUNTaschar( 1 )COMMITTRANPRINTAfter second COMMIT TRAN: + CAST( TRANC
6、OUNTas char( 1 )在结果中,可以看到每一个BEGINTRAN 语句都会使 TRANCOUNT增加 1 并且每一个COMMIT TRAN语句都会使其减少 1 。如前所述,一个值为0 的意味着没有打TRANCOUNT开的事务。 因此,在 TRANCOUNT值从 1 降到 0 时结束的事务发生在外层事务提交的时候。因此,每一个内部事务都需要提交。由于事务起始于第一个BEGINTRAN 并结束于最后一个 COMMIT TRAN,因此 最外层的事务决定了是否完全提交内部的事务。如果最外层的事务没有被提交,其中嵌套的事务也不会被提交。键入并执行以下批来检验事务回滚时所发生的情况:BEGINT
7、RANPRINTAfter 1st BEGIN TRAN: +CAST( TRANCOUNTaschar( 1 )BEGINTRAN3PRINTAfter 2nd BEGIN TRAN: +CAST( TRANCOUNTaschar( 1 )BEGINTRANPRINTAfter 3rd BEGIN TRAN: +CAST( TRANCOUNTaschar( 1 )UPDATE Data1SET value1= 1000000WHERE Id= 1COMMITTRANPRINTAfter first COMMIT TRAN: + CAST( TRANCOUNTaschar ( 1 )ROLLB
8、ACKTRANPRINTAfter ROLLBACK TRAN: +CAST( TRANCOUNTaschar( 1)SELECT*FROM Data1WHERE Id= 1 ;在这个示例中, 数据表 Data1 在一个嵌套事务中被更新,这会被立即提交。然后 ROLLBACKTRAN 被执行。ROLLBACK TRAN 将 TRANCOUNT减为 0 并回滚整个事务及其中嵌套的事务,无论它们是否已经被提交。因此,嵌套事务中所做的更新被回滚,数据没有任何改变。始终牢记, 在嵌套的事务中,只有最外层的事务决定着是否提交内部事务。每一个 COMMITTRAN 语句总是应用于最后一个执行的BEGIN
9、TRAN 。因此,对于每一个COMMIT TRAN,必须调用一个COMMITTRAN 来提交事务。ROLLBACKTRAN 语句总是属于最外层的事务,4并且因此总是回滚整个事务而不论其中打开了多少嵌套事务。正因为此, 管理嵌套事务很复杂。如果每一个嵌套存储过程都在自身中开始一个事务,那么嵌套事务大部分会发生在嵌套存储过程中。要避免嵌套事务,可以在过程开始处检查TRANCOUNT的值,以此来确定是否需要开始一个事务。如果TRANCOUNT大于 0 ,因为过程已经处于一个事务中并且调用实例可以在错误发生时回滚事务。5三、存储过程和触发器中回滚如果TRANCOUNT的值在存储过程完成时与过程执行时不
10、同,则会生成一个266 信息类错误。该错误不是由触发器中同一个条件生成的。当调用存储过程时,如果TRANCOUNT 为 1 或更大,并且该过程执行ROLLBACK TRANSACTION或 ROLLBACK WORK语句,则会产生266号错误。这是因为ROLLBACK回滚所有未完成的事务,并将TRANCOUNT减到0,该值比调用过程时要小。如果在触发器中发出ROLLBACK TRANSACTION:对当前事务中的那一点所做的所有数据修改都将回滚,包括触发器所做的修改。触发器继续执行ROLLBACK语句之后的所有其余语句。如果这些语句中的任意语句修改数据,则不回滚这些修改。执行其余的语句不会激发
11、嵌套触发器。在批处理中,所有位于激发触发器的语句之后的语句都不被执行。触发器中的ROLLBACK关闭并释放所有在包含激发触发器的语句的批处理中声明和打开的游标。这其中包括了在激发触发器的批处理所调用的存储过程中声明和打开的游标。在激发触发器的批处理之前的批处理中所声明的游标将只是关闭,但是在以下条件下,STATIC或 INSENSITIVE游标不关闭:CURSOR_CLOSE_ON_COMMIT设置为 OFF 。静态游标要么是同步游标,要么是完全填充的异步游标。当执行触发器时,触发器的操作总是好像有一个未完成的事务在起作用。如果激发触发器的语句是在隐性或显式事务中,则肯定会这样。在自动提交模式
12、下,也是如此。当语句开始以自动提交模式执行时,如果遇到错误,则会有隐含的BEGIN TRANSACTION语6句允许恢复该语句生成的所有修改。该隐含的事务对批处理中的其它语句没有影响,因为当语句完成时,该事务要么提交,要么回滚。但是,当调用触发器时,该隐含的事务将仍然有效。这意味着,只要触发器中发出BEGIN TRANSACTION语句,则实际上就开始了一个嵌套事务。因为当回滚嵌套事务时,嵌套的BEGIN TRANSACTION语句将被忽略,触发器中发出的ROLLBACK TRANSACTION总是回滚过去该触发器本身发出的所有BEGINTRANSACTION语句。 ROLLBACK回滚到最外
13、部的BEGIN TRANSACTION。若要在触发器中进行部分回滚,则即使总是以自动提交模式进行调用,也必须使用SAVE TRANSACTION语句。以下的触发器阐明了这一点:CREATE TRIGGER TestTrig ON TestTab FOR UPDATE ASSAVE TRANSACTION MyNameINSERT INTO TestAuditSELECT * FROM insertedIF (error 0)BEGINROLLBACK TRANSACTION MyNameEND这也影响触发器中BEGIN TRANSACTION语句后面的COMMIT TRANSACTION7语句
14、。因为BEGIN TRANSACTION启动一个嵌套事务,而随后的COMMIT语句只应用于该嵌套事务。如果在COMMIT之后执行ROLLBACK TRANSACTION语句,那么ROLLBACK将一直回滚到最外部的BEGIN TRANSACTION。以下的触发器阐明了这一点:CREATE TRIGGER TestTrig ON TestTab FOR UPDATE ASBEGIN TRANSACTIONINSERT INTO TrigTargetSELECT * FROM insertedCOMMIT TRANSACTIONROLLBACK TRANSACTION此触发器绝对不会在TrigTa
15、rget表中插入。 BEGIN TRANSACTION总是启动一个嵌套事务。 COMMIT TRANSACTION只提交嵌套事务,而下面的ROLLBACKTRANSACTION则一直回滚到最外部的BEGIN TRANSACTION。四、嵌套事务相关 SQL 语句:ROLLBACK可以回滚某个事务保存点(SAVE TRAN TranSave1),如 ROLLBACK TRANTranSave1 ,但是要明白的是,回滚事务保存点并不会使事务数TranCount减少,你嵌套了几个事务,它还是有几个事务数.特别注意,如果在父存储过程创建一个事务Tran1 ,然后在子存储过程执行ROLLBACK8TRA
16、N 后,子存储过程会抛出异常!事务只能在同一个存储过程里面创建、回滚和提交,不允许分离在不同的存储过程里面。基 于以上特点,我个人觉得嵌套事务的作用不大,SQL 的内部处理其实最终就是处理一个最外层的事务点。SQL 抛出事务相关的异常,并不是代码有何问题,而是在提醒我们注意事务的控制。我们只要采用TRY CATCH方式捕获相关异常就可以,我们只要确保设计的事务点能正常回滚或提交就OK 了。解决方法1:TRY CATCH捕获相关异常解决方法2:如果外部已经有事务了,就不再创建内部事务。我想SQL 的事务异常提醒就是为了告诉你,不能随便一个地方放事务。IF TRANCOUNT =0BEGIN TR
17、ANTRY CATCH注意事项:SQL 语句不加try catch ,即使出现异常, 后续的 SQL 语句也会执行。 但是一旦外部加了trycatch, 则会捕获异常,导致后续的SQL 语句没有执行。是否 SQL 的异常有分致命和普遍的,在没加try catch 的情况下,普通的可以继续往下走,但是致命的就不往下走了。这个和C# 编程语言有重大的不同,编程语言一旦出现异常,后9续代码就不再执行!在处理嵌套事务时,要特别注意,不论如何要确保事务被完整的关闭或被回滚!回滚比较好控制。无论有多少级事务数,只要ROLLBACK一次就可以 。不过如果是ROLLBACK TRAN TRANNAME, Tr
18、anName不是第一级的话,则会出现异常,等于没有执行 ROLLBACK操作。提交 就要特别注意了。BEGIN TRAN创建事务3 个,则必须COMMIT TRAN提交事务3次,才能确保事务数被完整提交。可以通过TRANCOUNT来查看当前事务数。一旦存储过程没有完整提交事务,则可能出现事务锁表的情况! 如果创建事务的进程销毁了,即使有未提交的事务,应该也销毁,算回滚了吧?BEGIN TRANSAVE TRANCOMMIT TRANROLLBACK TRAN嵌套事务示例: BEGINTRAN Tran1BEGIN TRAN Tran2COMMIT TRAN可以单独指定某个事务名,如Tran1 ,Tran2 进行提交。其实也没什么效果,即使Tran2提交成功了,只要将外层事务Tran1 回滚, Tran2 保存的数据照样被回滚!ROLLBACK TRAN 不能指定某个事务名进行回滚!只能 ROLLBACK TRAN或者ROLLBACK TRAN Tran1,也就说 只能回滚最外层的事务名。如果执行ROLLBACK TRANTran2 ,SQL 会提示 “无法回滚Tran2 。找不到该名称的事务或保存点”,出错的原因就是因为 Tran2 不是最外层事务。 总结一句话: ROLLBACK 要么就所有事务都回滚, 要么就回滚时异常,一个事务都没回滚 !10