嵌套事务的回滚与提交.docx
- 文档编号:4102058
- 上传时间:2022-11-27
- 格式:DOCX
- 页数:6
- 大小:18.82KB
嵌套事务的回滚与提交.docx
《嵌套事务的回滚与提交.docx》由会员分享,可在线阅读,更多相关《嵌套事务的回滚与提交.docx(6页珍藏版)》请在冰豆网上搜索。
嵌套事务的回滚与提交
嵌套事务的回滚与提交
一、嵌套事务和事务保存点的错误处理
1、对于嵌套事务。
1.外部起事务,内部起事务,内外都有TryCatch
内部出错:
如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。
外部出错:
如果外部事物出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。
注:
如果内部的事务不起事务名称,内部如果出错,将会回滚掉会话中的全部事务,而且报异常。
2.外部起事务,内部起事务,内部没有TryCatch
内部出错:
如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。
外部出错:
如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。
3.外部起事务,内部不起事务,但有TryCatch。
内部出错:
外部事物正常提交,外部事物不会进入ROLLBACK,内部出错之后的记录也会正常执行。
内部操作中,Try部分在错误出现之前的操作正常,Try部分在操作之后的操作不执行,然后进入Catch块中执行操作。
外部出错:
内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。
4.外部起事务,内部不起事务,但没有TryCatch.
内部出错:
如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。
外部出错:
如果内部事务出错,内部和外部事物全部回滚,外部回滚之前的操作全部不存在,但是之后的操作继续执行。
5.外部不起事务,内部起事务,但有TryCatch.
内部出错:
外部操作被正常执行,内部ROLLBACK操作前全部回滚,之后的操作正常执行。
外部出错:
出错操作之前的操作不会回滚,出错之后的操作不执行,跳入Catch块中,内部事务不会回滚。
6.外部不起事务,内部起事务,但没有TryCatch.
内部出错:
外部操作被正常执行,内部ROLLBACK操作前全部回滚。
由于没有catch块,所以外部操作全部执行。
外部出错:
内部事务正常提交,外部只有当条记录失败,其他操作正常执行,但是有严重错误报出来。
对于事务保存点
事务保存点只有SAVE和ROLLBACK操作,当外部调用内部保存点,内部出现问题不影响外部事务,外部操作正常执行。
当外部操作出现问题时,内部所有操作都回滚掉。
如:
外部起事务,内部起保存点,内外都有TryCatch
内部出错:
外部操作正常,不进入Catch,内部事务回滚到保存点,之后的继续执行。
外部出错:
如果外部事物在保存点之前出现异常,那么外部和内部所有操作回滚。
如果外部事物在保存点之前出现异常,由于保存点已经提交了事务,导致外部rollback找不到对应的事务点。
二、事务的嵌套
PRINT'Trancountbeforetransaction:
'+CAST(@@TRANCOUNTaschar
(1))
BEGINTRAN
PRINT'AfterfirstBEGINTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
BEGINTRAN
PRINT'AftersecondBEGINTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
COMMITTRAN
PRINT'AfterfirstCOMMITTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
COMMITTRAN
PRINT'AftersecondCOMMITTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
在结果中,可以看到每一个BEGINTRAN语句都会使@@TRANCOUNT增加1并且每一个COMMITTRAN语句都会使其减少1。
如前所述,一个值为0的@@TRANCOUNT意味着没有打开的事务。
因此,在@@TRANCOUNT值从1降到0时结束的事务发生在外层事务提交的时候。
因此,每一个内部事务都需要提交。
由于事务起始于第一个BEGINTRAN并结束于最后一个COMMITTRAN,因此最外层的事务决定了是否完全提交内部的事务。
如果最外层的事务没有被提交,其中嵌套的事务也不会被提交。
键入并执行以下批来检验事务回滚时所发生的情况:
BEGINTRAN
PRINT'After1stBEGINTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
BEGINTRAN
PRINT'After2ndBEGINTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
BEGINTRAN
PRINT'After3rdBEGINTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
UPDATEData1
SETvalue1=1000000
WHEREId=1
COMMITTRAN
PRINT'AfterfirstCOMMITTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
ROLLBACKTRAN
PRINT'AfterROLLBACKTRAN:
'+CAST(@@TRANCOUNTaschar
(1))
SELECT*FROMData1
WHEREId=1;
在这个示例中,数据表Data1在一个嵌套事务中被更新,这会被立即提交。
然后ROLLBACKTRAN被执行。
ROLLBACKTRAN将@@TRANCOUNT减为0并回滚整个事务及其中嵌套的事务,无论它们是否已经被提交。
因此,嵌套事务中所做的更新被回滚,数据没有任何改变。
始终牢记,在嵌套的事务中,只有最外层的事务决定着是否提交内部事务。
每一个COMMITTRAN语句总是应用于最后一个执行的BEGINTRAN。
因此,对于每一个COMMITTRAN,必须调用一个COMMITTRAN来提交事务。
ROLLBACKTRAN语句总是属于最外层的事务,并且因此总是回滚整个事务而不论其中打开了多少嵌套事务。
正因为此,管理嵌套事务很复杂。
如果每一个嵌套存储过程都在自身中开始一个事务,那么嵌套事务大部分会发生在嵌套存储过程中。
要避免嵌套事务,可以在过程开始处检查@@TRANCOUNT的值,以此来确定是否需要开始一个事务。
如果@@TRANCOUNT大于0,因为过程已经处于一个事务中并且调用实例可以在错误发生时回滚事务。
三、存储过程和触发器中回滚
如果@@TRANCOUNT的值在存储过程完成时与过程执行时不同,则会生成一个266信息类错误。
该错误不是由触发器中同一个条件生成的。
当调用存储过程时,如果@@TRANCOUNT为1或更大,并且该过程执行ROLLBACKTRANSACTION或ROLLBACKWORK语句,则会产生266号错误。
这是因为ROLLBACK回滚所有未完成的事务,并将@@TRANCOUNT减到0,该值比调用过程时要小。
如果在触发器中发出ROLLBACKTRANSACTION:
对当前事务中的那一点所做的所有数据修改都将回滚,包括触发器所做的修改。
触发器继续执行ROLLBACK语句之后的所有其余语句。
如果这些语句中的任意语句修改数据,则不回滚这些修改。
执行其余的语句不会激发嵌套触发器。
在批处理中,所有位于激发触发器的语句之后的语句都不被执行。
触发器中的ROLLBACK关闭并释放所有在包含激发触发器的语句的批处理中声明和打开的游标。
这其中包括了在激发触发器的批处理所调用的存储过程中声明和打开的游标。
在激发触发器的批处理之前的批处理中所声明的游标将只是关闭,但是在以下条件下,STATIC或INSENSITIVE游标不关闭:
CURSOR_CLOSE_ON_COMMIT设置为OFF。
静态游标要么是同步游标,要么是完全填充的异步游标。
当执行触发器时,触发器的操作总是好像有一个未完成的事务在起作用。
如果激发触发器的语句是在隐性或显式事务中,则肯定会这样。
在自动提交模式下,也是如此。
当语句开始以自动提交模式执行时,如果遇到错误,则会有隐含的BEGINTRANSACTION语句允许恢复该语句生成的所有修改。
该隐含的事务对批处理中的其它语句没有影响,因为当语句完成时,该事务要么提交,要么回滚。
但是,当调用触发器时,该隐含的事务将仍然有效。
这意味着,只要触发器中发出BEGINTRANSACTION语句,则实际上就开始了一个嵌套事务。
因为当回滚嵌套事务时,嵌套的BEGINTRANSACTION语句将被忽略,触发器中发出的ROLLBACKTRANSACTION总是回滚过去该触发器本身发出的所有BEGINTRANSACTION语句。
ROLLBACK回滚到最外部的BEGINTRANSACTION。
若要在触发器中进行部分回滚,则即使总是以自动提交模式进行调用,也必须使用SAVETRANSACTION语句。
以下的触发器阐明了这一点:
CREATETRIGGERTestTrigONTestTabFORUPDATEAS
SAVETRANSACTIONMyName
INSERTINTOTestAudit
SELECT*FROMinserted
IF(@@error<>0)
BEGIN
ROLLBACKTRANSACTIONMyName
END
这也影响触发器中BEGINTRANSACTION语句后面的COMMITTRANSACTION语句。
因为BEGINTRANSACTION启动一个嵌套事务,而随后的COMMIT语句只应用于该嵌套事务。
如果在COMMIT之后执行ROLLBACKTRANSACTION语句,那么ROLLBACK将一直回滚到最外部的BEGINTRANSACTION。
以下的触发器阐明了这一点:
CREATETRIGGERTestTrigONTestTabFORUPDATEAS
BEGINTRANSACTION
INSERTINTOTrigTarget
SELECT*FROMinserted
COMMITTRANSACTION
ROLLBACKTRANSACTION
此触发器绝对不会在TrigTarget表中插入。
BEGINTRANSACTION总是启动一个嵌套事务。
COMMITTRANSACTION只提交嵌套事务,而下面的ROLLBACKTRANSACTION则一直回滚到最外部的BEGINTRANSACTION。
四、嵌套事务
相关SQL语句:
ROLLBACK可以回滚某个事务保存点(SAVETRANTranSave1),如ROLLBACKTRANTranSave1,但是要明白的是,回滚事务保存点并不会使事务数@TranCount减少,你嵌套了几个事务,它还是有几个事务数.
特别注意,如果在父存储过程创建一个事务Tran1,然后在子存储过程执行ROLLBACKTRAN后,子存储过程会抛出异常!
事务只能在同一个存储过程里面创建、回滚和提交,不允许分离在不同的存储过程里面。
基于以上特点,我个人觉得嵌套事务的作用不大,SQL的内部处理其实最终就是处理一个最外层的事务点。
SQL抛出事务相关的异常,并不是代码有何问题,而是在提醒我们注意事务的控制。
我们只要采用TRYCATCH方式捕获相关异常就可以,我们只要确保设计的事务点能正常回滚或提交就OK了。
解决方法1:
TRYCATCH捕获相关异常
解决方法2:
如果外部已经有事务了,就不再创建内部事务。
我想SQL的事务异常提醒就是为了告诉你,不能随便一个地方放事务。
IF@TRANCOUNT=0
BEGINTRAN
TRYCATCH注意事项:
SQL语句不加trycatch,即使出现异常,后续的SQL语句也会执行。
但是一旦外部加了trycatch,则会捕获异常,导致后续的SQL语句没有执行。
是否SQL的异常有分致命和普遍的,在没加trycatch的情况下,普通的可以继续往下走,但是致命的就不往下走了。
这个和C#编程语言有重大的不同,编程语言一旦出现异常,后续代码就不再执行!
在处理嵌套事务时,要特别注意,不论如何要确保事务被完整的关闭或被回滚!
回滚比较好控制。
无论有多少级事务数,只要ROLLBACK一次就可以。
不过如果是ROLLBACKTRANTRANNAME,TranName不是第一级的话,则会出现异常,等于没有执行ROLLBACK操作。
提交就要特别注意了。
BEGINTRAN创建事务3个,则必须COMMITTRAN提交事务3次,才能确保事务数被完整提交。
可以通过@@TRANCOUNT来查看当前事务数。
一旦存储过程没有完整提交事务,则可能出现事务锁表的情况!
如果创建事务的进程销毁了,即使有未提交的事务,应该也销毁,算回滚了吧?
BEGINTRANSAVETRANCOMMITTRANROLLBACKTRAN嵌套事务示例:
BEGINTRANTran1BEGINTRANTran2COMMITTRAN可以单独指定某个事务名,如Tran1,Tran2进行提交。
其实也没什么效果,即使Tran2提交成功了,只要将外层事务Tran1回滚,Tran2保存的数据照样被回滚!
ROLLBACKTRAN不能指定某个事务名进行回滚!
只能ROLLBACKTRAN或者ROLLBACKTRANTran1,也就说只能回滚最外层的事务名。
如果执行ROLLBACKTRANTran2,SQL会提示“无法回滚Tran2。
找不到该名称的事务或保存点”,出错的原因就是因为Tran2不是最外层事务。
总结一句话:
ROLLBACK要么就所有事务都回滚,要么就回滚时异常,一个事务都没回滚!
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌套 事务 提交