sql server中嵌套事务

来源:互联网 发布:ubuntu ssh root 编辑:程序博客网 时间:2024/05/01 16:19
sql server中嵌套事务

我们在写事务时经常遇到的问题如下:

消息 266,级别 16,状态 2,过程 sp1,第 0 行
EXECUTE 后的事务计数指示BEGIN COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。
消息 3903,级别 16,状态 1,过程 sp2,第 15 行
ROLLBACK TRANSACTION 请求没有对应的BEGIN TRANSACTION

 如果这只是一个单独的事务引起的,那么很好解决,我们只要检查下是否遗漏了匹配的BEGIN tran 和 COMMIT tran即可,但是如果2个存储过程都是用事务写的,那么就即使每个存储过程的事务写法都正常,也会报这个错误,

这是因为只要子事务里有回滚语句:如ROLLBACK      那么全局的@@TRANCOUNT被直接置为0了,导致父事务提交时发现 @@TRANCOUNT=0  报错 ,sql server会认为当前不存在任何事务,在父存储过程中任何的COMMIT TRAN或

ROLLBACK 语句都会找不到它对应的 BEGIN TRAN   

下面我们用一个实例来看下:

假设有一张表,ID为非自增主键

复制代码
USE [TestDB]GO/****** Object:  Table [dbo].[test]    Script Date: 02/17/2013 15:44:35 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOSET ANSI_PADDING ONGOCREATE TABLE [dbo].[test](    [ID] [bigint] NOT NULL,    [UserID] [bigint] NULL,    [Name] [varchar](50) NULL, CONSTRAINT [PK_Table_1] PRIMARY KEY CLUSTERED (    [ID] ASC)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]) ON [PRIMARY]GOSET ANSI_PADDING OFFGO
复制代码

 

 我们常规的写一个插入的子存储过程如下:

复制代码
USE [TestDB]GO/****** Object:  StoredProcedure [dbo].[innertranv1]    Script Date: 02/17/2013 15:46:46 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO--内层事务存储过程,演示如何处理才能在嵌套的事务存储过程中正确处理事务ALTER PROCEDURE [dbo].[innertranv1]    @ID BIGINT ,    @UserID BIGINT ,    @Name VARCHAR(50)AS     BEGIN        SET XACT_ABORT ON            BEGIN TRAN                 IF(EXISTS(SELECT TOP 1 * FROM dbo.test WHERE ID=@ID))            BEGIN                ROLLBACK                        RETURN 0 ;          END                --业务逻辑开始                INSERT  dbo.test                ( ID, UserID, Name)        VALUES  ( @ID,                   @UserID,                  @Name                    )        --业务逻辑结束                IF @@error <> 0             BEGIN                  ROLLBACK                                                   RETURN 0;              END                  COMMIT   
     SET XACT_ABORT OFF;   
     RETURN 1 ;   END
复制代码

调用的父存储过程如下:

复制代码
USE [TestDB]GO/****** Object:  StoredProcedure [dbo].[outertranv2]    Script Date: 02/17/2013 16:09:09 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO-- =============================================-- Author:        <Author,,Name>-- Create date: <Create Date,,>-- Description:    <外层存储过程>-- =============================================ALTER PROCEDURE [dbo].[outertranv2]    @ID BIGINT ,    @UserID BIGINT ,    @Name VARCHAR(50)AS             BEGIN TRAN         DECLARE @result INT    EXEC @result = innertranv1 @ID =@ID, @UserID =@UserID, @Name = @Name    IF ( @result <= 0 )         BEGIN            ROLLBACK TRAN  ;                             RETURN ;        END     COMMIT TRAN        
复制代码

我们执行父存储过程:

复制代码
USE [TestDB]GODECLARE    @return_value intEXEC    @return_value = [dbo].[outertranv2]        @ID = 0,        @UserID = 0,        @Name = N'0'SELECT    'Return Value' = @return_valueGO
复制代码

第一次提交正常,再次执行就会出现如下错误:

消息 266,级别 16,状态 2,过程 innertranv1,第 0EXECUTE 后的事务计数指示 BEGINCOMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。消息 3903,级别 16,状态 1,过程 outertranv2,第 18ROLLBACK TRANSACTION 请求没有对应的 BEGIN TRANSACTION

 

如何解决?我们修改子存储过程如下:

复制代码
USE [TestDB]GO/****** Object:  StoredProcedure [dbo].[innertran]    Script Date: 02/17/2013 16:26:26 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO--内层事务存储过程,演示如何处理才能在嵌套的事务存储过程中正确处理事务ALTER PROCEDURE [dbo].[innertran]    @ID BIGINT ,    @UserID BIGINT ,    @Name VARCHAR(50)AS     BEGIN        DECLARE @TRANCOUNT int=(select @@TRANCOUNT)            SET XACT_ABORT ON        SET @TRANCOUNT=(select @@TRANCOUNT)        PRINT '未进入子事务前全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))            BEGIN TRAN tran1      --开始事务         SAVE TRAN tranpoint   --保存事务点        SET @TRANCOUNT=(select @@TRANCOUNT)        PRINT '进入子事务后全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))                   IF(EXISTS(SELECT TOP 1 * FROM dbo.test WHERE ID=@ID))            BEGIN                ROLLBACK TRAN tranpoint ;   --回滚保存点的事务                   COMMIT TRAN tran1 ;            --提示当前事务                SET @TRANCOUNT=(select @@TRANCOUNT)                  PRINT '回滚子事务后全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))                                               RETURN 0 ;          END                --业务逻辑开始                INSERT  dbo.test                ( ID, UserID, Name)        VALUES  ( @ID,                   @UserID,                  @Name                    )        --业务逻辑结束                IF @@error <> 0             BEGIN                  ROLLBACK TRAN tranpoint ; --回滚保存点的事务                   COMMIT TRAN tran1 ;          --提示当前事务                  SET @TRANCOUNT=(select @@TRANCOUNT)                  PRINT '回滚子事务后全局@@TRANCOUNTT'+CAST(@TRANCOUNT AS VARCHAR(50))                                                              RETURN 0;              END                  COMMIT TRAN tran1 ;            SET XACT_ABORT OFF;            SET @TRANCOUNT=(select @@TRANCOUNT)          PRINT '提交子事务后全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))                   RETURN 1 ;    END
复制代码

 

父过程如下:  

复制代码
USE [TestDB]GO/****** Object:  StoredProcedure [dbo].[outertran]    Script Date: 02/17/2013 16:27:13 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO-- =============================================-- Author:        <Author,,Name>-- Create date: <Create Date,,>-- Description:    <外层存储过程>-- =============================================ALTER PROCEDURE [dbo].[outertran]    @ID BIGINT,    @UserID BIGINT,    @Name VARCHAR(50)AS     DECLARE @TRANCOUNT int=(select @@TRANCOUNT)    PRINT '未进入父事务前全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))                BEGIN TRAN     SET @TRANCOUNT=(select @@TRANCOUNT)     PRINT '进入父事务后全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))                DECLARE @result INT    EXEC @result = innertran @ID = @ID, @UserID = @UserID, @Name =@Name    IF ( @result <= 0 )         BEGIN            ROLLBACK TRAN  ;            SET @TRANCOUNT=(select @@TRANCOUNT)             PRINT '回滚父事务后全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))                                     RETURN ;        END     COMMIT TRAN     SET @TRANCOUNT=(select @@TRANCOUNT)     PRINT '提交父事务后全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))
复制代码

 

 调用父存储过程:

复制代码
USE [TestDB]GODECLARE    @return_value intEXEC    @return_value = [dbo].[outertran]        @ID = 0,        @UserID = 0,        @Name = N'0'SELECT    'Return Value' = @return_valueGO
复制代码

结果如下:

未进入父事务前全局@@TRANCOUNT:0进入父事务后全局@@TRANCOUNT:1未进入子事务前全局@@TRANCOUNT:1进入子事务后全局@@TRANCOUNT:2回滚子事务后全局@@TRANCOUNT:1回滚父事务后全局@@TRANCOUNT:0

不会再报"EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配"之类的错误了,实际上就是在每个嵌套的子过程中标明当前事务点,每个子事务 只提交/回滚 子事务点,而不是回滚整个事务!

 

另外一篇文章:http://stackoverflow.com/questions/2073737/nested-stored-procedures-containing-try-catch-rollback-pattern/2074139#2074139

http://www.agile-code.com/blog/an-easy-way-to-avoid-sql-server-nested-transactions/

0 0