sqlserver 多重嵌套事務的存儲過程處理與解決方法

對於帶有事務的嵌套存儲過程的處理網上的確有很多資料可參考。但大多是片面或過於字面化。對不清楚如何解決這種問題的同仁來說沒有多大用處。最近正好工作不忙,就研究了下,發現sqlserver的嵌套事務存儲過程在處理時確實有一定難度。原因在於微軟的sqlserver在帶有嵌套的事務方面存在以下幾點特徵:

 

1。 sqlserver 忽略提交內部事務。即無論內層事務是否提交,外層事務提交後,所有內層事務都會提交,反之外層事務回滾了,內層事務即使提交過,也會回滾。以外層事務的最後執行的操作(commit or rollback)爲準。

 

2。每個commit的調用都只對應最後執行的begin transaction。意思就是每個commit對應與自己開始的begin transaction。打個比方:內層commit對應的是內層begin  transaction,而不會影響外層。即使有更多層的嵌套也是如此。

 

3。rollback transaction 中的 transaction_name (事務名)只能引用最外層的transaction_name。引用內層或更多嵌套內層的事務名是不允許的。比如:內層begin transaction xx(事務名),內層rollback transaction 時不能引用xx,而只能引用最外層的事務名。

 

4。@@trancount記錄事務的嵌套級數。每begin transaction一次 都會使@@trancount加1,每commit transaciton一次都會使@@trancount減1,而rollback transaction 則會直接使@@trancount爲0,無論是嵌套事務還是單一事務。可通過檢查@@trancount是否爲0來確實是否還在事務中,大於0表明還在事務中。

 

由於以上幾的特徵的限制,使得嵌套的事務處理不那麼容易。單一的事務存儲過程是很好解決的。但如果需要調用已經存在的存儲過程且自身已有事務處理了,那麼問題就出現了(二層嵌套事務的存儲過程執行結果):

原因在於:每個事務在開始與提交時@@trancount要對應,如果開始事務的@@trancount與commit時的@@trancount不對應,就會出現以上的問題。說白了就是開始事務時@@trancount爲1 ,那麼在commit時也要爲1,commit之後纔會使@@trancount減1。

要解決嵌套事務的存儲過程,避免以上的問題,個人總結兩個方式:

1。使用@@trancount  + 事務保存點 解決。 外層事務傳遞當前@@trancount值給內層事務,根據傳遞過來的@@trancount值是否大於0來選擇是否保存事務點。大於0則保存,用於內層回滾到此事務點,等於0說明沒開始事務,則直接開始內層的事務處理。內層事務處理中如果有錯誤,則根據傳遞過來的@@trancount值判斷是回滾整個事務還是回滾到事務的保存點。

 

2。使用事務保存點 + rollback transactin 事務保存點 + 強制內層事務提交 解決。 此方式與1差別不多大,只是不使用@@trancount全局函數。內層事務開始後立即保存事務點。出錯後直接回滾到事務保存點,再交由外層的事務來決定最後的執行操作。

 

以下是第一種方式的sql腳本的詳細過程:

 

--測試用的數據庫test
use test;
GO

--刪除測試用的表
if  EXISTS 
(
 SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'AgeValues')
)

begin

 DROP TABLE AgeValues;

end
GO

--內層存儲過程已存在則先刪除再創建
IF exists
(
 SELECT * from sys.objects WHERE object_id = object_id(N'InnerProcedure')
)

begin

 drop PROCEDURE InnerProcedure;

end

--外層存儲過程已存在則先刪除再創建
IF exists
(
 SELECT * from sys.objects where object_id = object_id(N'OuterProcedure')
)

begin

 drop PROCEDURE OuterProcedure;

end


--創建測試用的表
CREATE table AgeValues(age int not null check (age < 200));
GO

--設置不返回受影響的行數
SET NOCOUNT on;
GO

 

 

 

/*******************************************************************************
作者:董輝
類型:新建
日期:2012-7-17 16:16
說明:用於解決嵌套事務的存儲過程的方式 1


*******************************************************************************/

--內層事務存儲過程,可直接執行,也可被其它帶有事務的存儲過程調用
create procedure InnerProcedure
(
 @TranCount int,   --當前@@trancount值
 
 @in_AgeValue int, --年齡
 
 @out_Result varchar(500) output --結果(OK正確,其它錯誤)
)

as

begin

 begin try --開始捕獲異常
 
  print ('開始內層事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
  
  begin transaction InnerTran --開始事務
 
  save transaction InnerTranPoint --保存事務點,用於回滾
  
  print ('開始內層事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
  
  print ('開始執行InnerProcedure存儲過程的數據處理......');
  
  insert INTO AgeValues VALUES(@in_AgeValue);
  
  IF @@error <> 0 --如果執行有錯誤,則@@error顯示錯誤碼,無錯誤時顯示0
  
   begin 
   
    if @TranCount > 0   
          
     begin
     
      set @out_Result = '執行出錯,錯誤碼爲:' + cast(@@error AS VARCHAR);
      
      print ('被其它事務存儲過程調用時,在執行回滾事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
      
      rollback transaction InnerTranPoint; --回滾到事務的保存點
      
      print ('被其它事務存儲過程調用時,在執行回滾事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
      
      print ('被其它事務存儲過程調用時,在執行提交事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
      
      commit transaction InnerTran; --把當前的事務提交
      
      print ('被其它事務存儲過程調用時,在執行提交事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
     
     end
     
    else if @TranCount = 0 
    
     begin
     
      set @out_Result = '執行出錯,錯誤碼爲:' + cast(@@error AS VARCHAR);
      
      print ('直接調用時,在執行回滾事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
      
      rollback transaction InnerTran;
      
      print ('直接調用時,在執行回滾事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
     
     end
   
   end
   
  else --沒有錯誤時則直接提交當前事務,並設置返回信息爲1
  
   begin
   
    set @out_Result = 'OK';
    
    print ('執行沒有出錯時,在提交事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
    
    commit transaction InnerTran;
    
    print ('執行沒有出錯時,在提交事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
   
   end
  
 
 end try  --結束捕獲異常
 
 begin catch --處理異常
 
  if @TranCount > 0   
        
   begin
   
    set @out_Result = 'catch內捕獲:執行出錯,錯誤碼爲:' + cast(ERROR_NUMBER() AS VARCHAR) + '  錯誤信息爲:' + error_message();
    
    print ('被其它事務存儲過程調用時,在執行回滾事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
    
    rollback transaction InnerTranPoint; --回滾到事務的保存點
    
    print ('被其它事務存儲過程調用時,在執行回滾事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
    
    print ('被其它事務存儲過程調用時,在執行提交事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
    
    commit transaction InnerTran; --把當前的事務提交
    
    print ('被其它事務存儲過程調用時,在執行提交事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
   
   end
   
  else if @TranCount = 0 
  
   begin
   
    set @out_Result = '執行出錯,錯誤碼爲:' + cast(ERROR_NUMBER() AS VARCHAR) + '  錯誤信息爲:' + error_message();
    
    print ('直接調用時,在執行回滾事務前,@@trancount計數爲:' + cast(@@trancount as varchar));
    
    rollback transaction InnerTran;
    
    print ('直接調用時,在執行回滾事務後,@@trancount計數爲:' + cast(@@trancount as varchar));
   
   end
 
 end catch   --處理異常

end
go


--外層事務存儲過程,用來調用其它帶事務的存儲過程
create procedure OuterProcedure
(
 @in_Age int --年齡
)

as

begin

 declare @TranCount int; --當前@@trancount值
 
 declare @Result varchar(500); --用來接收返回的信息
 
 print ('外層事務開始前,@@trancount計數爲:' + cast(@@trancount as varchar));
 
 begin transaction OuterTran --開始事務
 
 print ('外層事務開始後,@@trancount計數爲:' + cast(@@trancount as varchar));
 
 print ('開始執行OuterProcedure存儲過程的數據處理......');
 
 set @TranCount = @@trancount; --記錄當前@@trancount值
 
 exec InnerProcedure @TranCount,@in_Age,@Result output; 
  
 IF @Result <> 'OK' --有錯誤
 
  BEGIN
  
   print ('外層事務回滾前,@@trancount計數爲:' + cast(@@trancount as varchar));
   
   rollback transaction OuterTran;
   
   print ('外層事務回滾後,@@trancount計數爲:' + cast(@@trancount as varchar));
   
   print ('外層:執行出錯,已回滾!');
  
  end
  
 else
 
  begin
  
   print ('外層事務提交前,@@trancount計數爲:' + cast(@@trancount as varchar));
   
   commit transaction  OuterTran;
   
   print ('外層事務提交後,@@trancount計數爲:' + cast(@@trancount as varchar));
   
   print ('外層:執行成功,已提交!');
  
  end
  
 print (char(10) + @Result);

end
go

 

--開始測試(值大於200則不能插入表中。AgeValues表age字段約束爲不能大於200)
declare @k int = 300;
exec OuterProcedure @k;
go

 

 

 

這個是第二種方式的sql腳本詳細過程:

 

/***************************************************************************
作者:董輝
類型:新建
日期:2012-7-17 9:26
說明:用於解決嵌套事務的存儲過程的方式 2
   outertran事務存儲過程在調用 innertran 事務存儲過程與調用 innertranDemo
   事務存儲過程有區別,調用第一個時由於內層出錯回滾了當前事務,這時
   @@trancount被回滾到了0,外層再回滾時就會出錯。而調用第二個事務存儲過程
   時,由於內層事務存儲過程保存了事務點,出錯時回滾到事務點,這樣外層就不
   會出數計數不匹配的錯誤。
   
   BEGIN distributed transaction  指定一個由 Microsoft 分佈式事務處理協調器 
   (MS DTC) 管理的 Transact-SQL 分佈式事務的起始


***************************************************************************/


--內層事務存儲過程,演示如何處理才能在嵌套的事務存儲過程中正確處理事務
alter procedure innertranDemo

as

begin

 begin tran tran1 --開始事務
 
 save tran t   --保存事務點
 
 insert into t1 VALUES(1);
 
 if @@error <> 0
 
  begin
  
   rollback tran t; --回滾保存點的事務
   
   commit tran tran1; --提示當前事務
   
   return -1;
  
  end
  
 insert INTO t1 VALUES(189);
 
 IF @@error <> 0
 
  begin
  
   rollback tran t;
   
   commit tran tran1;
   
   return -1;
  
  end
  
 commit tran tran1;
 
 return 1;

end

 

--外層事務存儲過程
alter procedure outertran

as

begin

 -- with mark '開始事務'  指定在日誌中標記事務
 begin tran tran2 with mark '開始事務' --開始事務,使@@trancount加1
 
 insert INTO t1 VALUES(2);
 
 declare @invikecode int;
 
 exec @invikecode = innertranDemo;--執行innertran與執行innertranDeom會有不同的結果
 
 IF @invikecode = -1
 
  begin
  
   rollback tran tran2;
   
   print ('回滾,當前計數爲:' + cast(@@trancount as varchar));
  
  end
 else
 
  begin
  
   commit tran tran2;
  
   print ('提交,當前計數爲:' + cast(@@trancount as varchar));
   
  end

end

 


--測試
exec outertran;

 

總結下來,對於嵌套事務的存儲過程解決起來無非就是利用@@trancount來判斷事務的當前嵌套狀態,來決定內層的事務是回滾到事務點,還是直接全部回滾。內層回滾時是可以直接引用外層的事務名進行操作的。但不允許引用自己內層的事務名。如果說有這種情況,就是別人寫好的正常執行的單個帶事務操作的存儲過程,你去調用,但你自己的存儲過程也需要事務處理。這種情況再怎麼解決也是需要去修改它的存儲過程的。不是修改邏輯處理,而是按上面的方式進行能正常用於嵌套事務的處理方式來修改。畢竟單一事務與多重嵌套事務在處理上還是存在很大差別的。

 

轉自: http://f.dataguru.cn/blog-11439-1499.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章