sql server中嵌套事务*

转自 https://www.cnblogs.com/guanjie20/archive/2013/02/17/2914488.html

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

消息 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 on
go

set quoted_identifier on
go

set ansi_padding on
go

create 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]

go

set ansi_padding off
go

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

use [testdb]
go
/****** object:  storedprocedure [dbo].[innertranv1]    script date: 02/17/2013 15:46:46 ******/
set ansi_nulls on
go
set quoted_identifier on
go

--内层事务存储过程,演示如何处理才能在嵌套的事务存储过程中正确处理事务
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 on
go
set quoted_identifier on
go

-- =============================================
-- 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]
go

declare    @return_value int

exec    @return_value = [dbo].[outertranv2]
        @id = 0,
        @userid = 0,
        @name = n'0'

select    'return value' = @return_value

go

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

消息 266,级别 16,状态 2,过程 innertranv1,第 0 行
execute 后的事务计数指示 begin 和 commit 语句的数目不匹配。上一计数 = 1,当前计数 = 0。
消息 3903,级别 16,状态 1,过程 outertranv2,第 18 行
rollback transaction 请求没有对应的 begin transaction。

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

use [testdb]
go
/****** object:  storedprocedure [dbo].[innertran]    script date: 02/17/2013 16:26:26 ******/
set ansi_nulls on
go
set quoted_identifier on
go
--内层事务存储过程,演示如何处理才能在嵌套的事务存储过程中正确处理事务
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 on
go
set quoted_identifier on
go
-- =============================================
-- 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]
go

declare    @return_value int

exec    @return_value = [dbo].[outertran]
        @id = 0,
        @userid = 0,
        @name = n'0'

select    'return value' = @return_value

go

结果如下:

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

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

实际开发中还是会出现事务错乱的情况,如在try…catch…中

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐