关于调用方有事务,被调用的SP中也有事务,在嵌套SP中回滚代码的报错处理,好文推荐

SQL报错异常:Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.

--首先明确一点,在SQL中开启事务时,Begin Tran时,@@TRANCOUNT会加1,Commit Tran时@@TRANCOUNT会减1,但是当ROLLBACK TRAN时会把@@TranCount直接设置为0,
--对于外部有开启事务的SQL代码来说,如果调用的嵌套SP中也有开启事务的话,进到嵌套SP中的时候,@@tranCount=1(假如只要一层嵌套,
--此处等于1是因为外部调用该SP的地方使用了Begin Tran来开启了一个事务),但是如果在嵌套SP中使用了ROLLBACK TRAN回滚代码时,会把@@tranCount直接设置为0,
--在该嵌套SP中调用结束时就会导致@@tranCount的数量进来和出去时不一致了,SQL就会报错了,所以在嵌套SP中如果需要回滚,应使用回滚事务保存点,而不是ROLLBACK TRAN。
--如果在嵌套SP中如果需要回滚,则应该使用ROLLBACK TRAN pp,
--如果直接使用ROLLBACK TRAN,则执行ROLLBACK TRAN后,会把@@TRANCOUNT直接设置为0,
--这样如果外层调用该SP的地方有Begin Tran的话,会导致SQL报出类似于
--“Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.”的异常,
--因为进入该方法时,外部sql的Begin tran会使@@TranCount加1,但执行此处的ROLLBACK TRAN后,会把@@TRANCOUNT直接设置为0,
--结束调用该SP返回时,事务的数量调用该SP和结束时就不一样了,所以SQL报异常了。
--解决此问题的方法就是在嵌套的SP中如果有事务,则在嵌套SP中,开启事务的地方保存事务点,如save tran pp;
--在Catch中或需要回滚代码的地方加上ROLLBACK TRAN pp;就是回滚当前的事务代码,这个代码不会改变@@TranCount的值。
--然后再写COMMIT TRAN;,这样就会把嵌套的SP中对应的BEGIN TRAN给释放了,即BEGIN TRAN使@@TranCount加1,COMMIT TRAN;使@@TranCount减1;
--这样调用和结束调用嵌套SP时的@@TranCount数量就一致了,就不会报上面的异常了。你没有看错,是在嵌套的SP中回滚时使用回滚事务保存点,
--且需要Commint事务才可以正常运行。也就是说,如果调用的外部代码有起事务,且在嵌套SP中也有事务的话,嵌套SP中的事务不管提交还是回滚,都需要提交事务才可以,不然就会报上面的错误。
--实践证明,调用嵌套SP的地方不止是SQL的sp会有这个问题,C#代码中起一个事务,再来调用有事务处理的嵌套SP,
--在嵌套SP中直接写回滚语句ROLLBACK TRAN也会有相同问题,都可以使用回滚事务点来解决。切记嵌套的SP中使用事务时,不管是否回滚都需要提交事务才可以。
--当然,如果调用方的SP或SQL中没有起事务,被调用的SP中有事务,则使用ROLLBACK TRAN没有任何影响,嵌套事务都有起事务一定要特别注意,确实是实践出真知。

--补充一点,回滚事务保存点ROLLBACK TRAN pp时,只会回滚从save tran pp到ROLLBACK TRAN pp中间的代码处理,其他部分的代码段还是能正常提交上去的
--因此使用回滚事务保存点的sql时,一定要把需要回滚的代码放在save tran pp到ROLLBACK TRAN pp的中间,如果在这个外面就不会回滚了,而会提交上去,因为后面有Commit Tran语句执行。
--另外,经测试,ROLLBACK TRAN pp回滚事务保存点的SQL,在单个SP单独执行时也一样适用,使用此方法回滚事务可能更严谨一点。


---创建测试表

IF EXISTS ( SELECT * FROM sys.tables WHERE name = ‘tt‘ )
    DROP TABLE dbo.tt ;

CREATE TABLE dbo.tt ( ID INT IDENTITY , Name NVARCHAR (100), TransCount INT ) ;
GO

--查询结果
SELECT * FROM dbo.tt

---创建子存储过程
IF EXISTS ( SELECT * FROM sys.procedures WHERE name = ‘S_proc‘ )
    DROP PROC S_proc ;
GO

CREATE PROC S_proc
AS
BEGIN
    PRINT(‘s-init-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    BEGIN TRAN;
    save tran pp;
    PRINT(‘s-prev-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    DECLARE @p2 INT = @@TRANCOUNT;
    INSERT INTO dbo.tt ( Name, TransCount ) SELECT ‘查询2‘, @p2 ;

    --如果此处要回滚,则应该使用ROLLBACK TRAN pp,
    --如果直接使用ROLLBACK TRAN,则执行ROLLBACK TRAN后,会把@@TRANCOUNT直接设置为0,
    --这样如果外层调用该SP的地方有Begin Tran的话,会导致SQL报出类似于
    --“Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.”的异常,
    --因为进入该方法时,外部sql的Begin tran会使@@TranCount加1,但执行此处的ROLLBACK TRAN后,会把@@TRANCOUNT直接设置为0,
    --结束调用该SP返回时,事务的数量调用该SP和结束时就不一样了,所以SQL报异常了。
    --解决此问题的方法就是在嵌套的SP中如果有事务,则在嵌套SP中,开启事务的地方保存事务点,如save tran pp;
    --在Catch中或需要回滚代码的地方加上ROLLBACK TRAN pp;就是回滚当前的事务代码,这个代码不会改变@@TranCount的值。
    --然后再写COMMIT TRAN;,这样就会把嵌套的SP中对应的BEGIN TRAN给释放了,即BEGIN TRAN使@@TranCount加1,COMMIT TRAN;使@@TranCount减1;
    --这样调用和结束调用嵌套SP时的@@TranCount数量就一致了,就不会报上面的异常了。你没有看错,是在嵌套的SP中回滚时使用回滚事务保存点,
    --且需要Commint事务才可以正常运行。也就是说,如果调用的外部代码有起事务,且在嵌套SP中也有事务的话,嵌套SP中的事务不管提交还是回滚,都需要提交事务才可以,不然就会报上面的错误。
    --实践证明,调用嵌套SP的地方不止是SQL的sp会有这个问题,C#代码中起一个事务,再来调用有事务处理的嵌套SP,
    --在嵌套SP中直接写回滚语句ROLLBACK TRAN也会有相同问题,都可以使用回滚事务点来解决。切记嵌套的SP中使用事务时,不管是否回滚都需要提交事务才可以。
    --当然,如果调用方的SP或SQL中没有起事务,被调用的SP中有事务,则使用ROLLBACK TRAN没有任何影响,嵌套事务都有起事务一定要特别注意,确实是实践出真知。

    --补充一点,回滚事务保存点ROLLBACK TRAN pp时,只会回滚从save tran pp到ROLLBACK TRAN pp中间的代码处理,其他部分的代码段还是能正常提交上去的
    --因此使用回滚事务保存点的sql时,一定要把需要回滚的代码放在save tran pp到ROLLBACK TRAN pp的中间,如果在这个外面就不会回滚了,而会提交上去,因为后面有Commit Tran语句执行。
    --另外,经测试,ROLLBACK TRAN pp回滚事务保存点的SQL,在单个SP单独执行时也一样适用,使用此方法回滚事务可能更严谨一点。

--切记此处不能直接写ROLLBACK TRAN;而应该写下面的回滚事务保存点的ROLLBACK TRAN pp;
    ROLLBACK TRAN pp;
    PRINT(‘s-after-rollback-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    --切记此处不管是否回滚,都需要提交事务,否则@@TRANCOUNT不会减1,而起事务时@@TRANCOUNT加1了,就会导致进来和出去的事务数量不一致的错误了。
    COMMIT TRAN;
    PRINT(‘s-after-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    RETURN ;
END ;

---创建主存储过程
IF EXISTS ( SELECT * FROM sys.procedures WHERE name = ‘P_proc‘ )
    DROP PROC P_proc ;
GO

CREATE PROC P_proc
AS
BEGIN
    DELETE FROM dbo.tt;

    PRINT(‘p-init-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    BEGIN TRAN ;
    PRINT(‘p-prev-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    DECLARE @p1 INT = @@TRANCOUNT;
    INSERT INTO dbo.tt ( Name, TransCount ) SELECT ‘查询1‘, @p1 ;
    PRINT(‘p-after-insert-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)))
    EXEC dbo.S_proc ;
    PRINT(‘p-after-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    COMMIT TRAN;
    PRINT(‘p-last-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
    RETURN ;
END ;
GO

--使用了保存点的回滚的测试SP
ALTER PROC S_saveTranBeforeandAfterTest
AS
BEGIN
PRINT(‘s-init-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
BEGIN TRAN;

PRINT(‘s-prev-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
DECLARE @p2 INT = @@TRANCOUNT;

INSERT INTO dbo.tt ( Name, TransCount ) SELECT ‘查询2-before save‘, @p2 ;
save tran pp;
INSERT INTO dbo.tt ( Name, TransCount ) SELECT ‘查询2-after save‘, @p2 ;


--补充一点,回滚事务保存点ROLLBACK TRAN pp时,只会回滚从save tran pp到ROLLBACK TRAN pp中间的代码处理,其他部分的代码段还是能正常提交上去的
--因此使用回滚事务保存点的sql时,一定要把需要回滚的代码放在save tran pp到ROLLBACK TRAN pp的中间,如果在这个外面就不会回滚了,而会提交上去,因为后面有Commit Tran语句执行。
--另外,经测试,ROLLBACK TRAN pp回滚事务保存点的SQL,在单个SP单独执行时也一样适用,使用此方法回滚事务可能更严谨一点。

--切记此处不能直接写ROLLBACK TRAN;而应该写下面的回滚事务保存点的ROLLBACK TRAN pp;
ROLLBACK TRAN pp;
PRINT(‘s-after-rollback-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
--切记此处不管是否回滚,都需要提交事务,否则@@TRANCOUNT不会减1,而起事务时@@TRANCOUNT加1了,就会导致进来和出去的事务数量不一致的错误了。
COMMIT TRAN;
PRINT(‘s-after-‘ + CAST(@@TRANCOUNT AS NVARCHAR(10)));
RETURN ;
END ;



原文地址:https://www.cnblogs.com/itjeff/p/12106447.html

时间: 2024-10-09 20:44:34

关于调用方有事务,被调用的SP中也有事务,在嵌套SP中回滚代码的报错处理,好文推荐的相关文章

Qt打开外部程序和文件夹需要注意的细节(Qt调用VC写的动态库,VC需要用C的方式输出函数,否则MinGW32编译过程会报错)

下午写程序中遇到几个小细节,需要在这里记录一下. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 QProcess *process = new QProcess(this);     QFileInfo fileinfo(appUrl);     QString appPath = QApplication::applicationDirPath()+SAVEDIR+"/"+fileinfo.fileName();     bool res = proce

SAP 事务代码 ME31K 报错 - 不含来自带工厂分配的供应商的物料的采购没有被定义 -

近日收到业务团队的报错说,试图创建合同,报错如下: 不含来自带工厂分配的供应商的物料的采购没有被定义 这个中文翻译实在太难受了.进入英文版SAP系统,报错信息如下: Procurement w/o material from vendor with plant assignment not defined 详细报错信息: Procurement w/o material from vendor with plant assignment not defined Message no. 06806

C#5.0新增功能02 调用方信息

连载目录    [已更新最新开发文章,点击查看详细] 通过使用调用方信息特性,可获取有关方法的调用方的信息. 可以获取源代码的文件路径.源代码中的行号和调用方的成员名称. 此信息有助于跟踪.调试和创建诊断工具.若要获取此信息,可以使用应用于可选参数的特性,每个特性都具有默认值. 下表列出在 System.Runtime.CompilerServices 命名空间中定义的调用方信息特性: 特性 描述 类型 CallerFilePathAttribute 包含调用方的源文件的完整路径. 这是编译时的

lua报错,看到报错信息有tail call,以为和尾调用有关,于是查了一下相关知识

尾调用是指在函数return时直接将被调函数的返回值作为调用函数的返回值返回,尾调用在很多语言中都可以被编译器优化, 基本都是直接复用旧的执行栈, 不用再创建新的栈帧, 原理上其实也很简单, 因为尾调用在本质上看的话,是整个子过程调用的最后执行语句, 所以之前的栈帧的内容已经不再需要, 完全可以被复用.报错的回溯日记,因为旧的执行栈已经没了,所以报错日记只显示(tail call).一般调用栈的长度为1M到2M,保存了调用过程中的参数和相关环境,如果递归调用太长,就会溢出.尾调用就能解决递归函数

ie8 报错:意外地调用了方法或属性访问

在某场景中一句简单的js: $("#changeOption").text("增加"); 在 IE8 下面报错:'意外地调用了方法或属性访问' 改成:$("#changeOption").html("增加"); 报同样的错. 改成:document.getElementById('changeOption').innerText="增加"; 同样报错:"未知的运行时错误" 最后改成:$(

Spring事务(一)JDBC方式下的事务使用示例

摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.创建数据表结构 二.创建对应数据表的PO 三.创建表与实体间的映射 四.创建数据操作接口 五.创建数据操作接口实现类 六 .创建Spring配置文件 七.测试 Spring声明式事务让我们从复杂的事务处理中得到解脱,使我们再也不需要去处理获得连接.关闭连接.事务提交和回滚等操作,再也不需要在与事务相关的方法中处理大置的 try...catch...finally代码.

SQL Server中事务transaction如果没写在try catch中,就算中间语句报错还是会提交

假如我们数据库中有两张表Person和Book Person表: CREATE TABLE [dbo].[Person]( [ID] [int] IDENTITY(1,1) NOT NULL, [Code] [nvarchar](50) NULL, [Name] [nvarchar](50) NULL, [CreateTime] [datetime] NULL, [UpdateTime] [datetime] NULL, CONSTRAINT [PK_Person] PRIMARY KEY CL

sql事务的调用

一.数据库的SQL USE [Text]GO/****** Object: StoredProcedure [dbo].[mon] Script Date: 2017-01-03 15:59:28 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOALTER proc [dbo].[mon] @toID int, --接收转账的账户 @fromID int , --转出自己的账户 @momeys money, --转账的金额 @back in

解决WCF 调用方未由服务器进行身份验证或消息包含无效或过期的安全上下文令牌

错误描述: 1. WCF:调用方未由服务器进行身份验证 2. 无法处理消息.这很可能是因为操作“http://tempuri.org/ISCCLSvc/GetCarriersByWareHouse”不正确,或因为消息包含无效或过期的安全上下文令牌,或因为绑定之间出现不匹配.如果由于未处于活动状态导致服务中止了该通道,则安全上下文令牌无效.若要防止服务永久中止闲置会话,请增加服务终结点绑定上的接收超时. 3.或并发测试时,高并发出现问题2,实际案例:公司测试部门结果是并发到50就会出现问题2错误,