1. 程式人生 > >[SQL Server]巢狀事務與分散式事務

[SQL Server]巢狀事務與分散式事務

 Sql Server支援巢狀事務:也就是說在前一事務未完成之前可啟動一個新的事務,只有在外層的Commit Tran語句才會導致資料庫的永久更改。
請嘗試執行以下語句:

BEGIN  TRAN  tr0
       BEGIN TRAN tr1
       ROLLBACK TRAN tr1 
ROLLBACK TRAN tr0

執行結果:伺服器: 訊息 3903,級別 16,狀態 1,行 5
ROLLBACK TRANSACTION 請求沒有對應的 BEGIN TRANSACTION。

原因分析:
1)    Sql Server把每個連線開啟的事務數目記錄在全域性變數@@trancount中,就象計數器一樣,每個Begin Tran語句會讓@@trancount自增1,每個Commit Tran語句會讓@@trancount自減1,只有最外層的Commit Tran(當@@trancount=1)會將更改影響到資料庫中,而不再儲存在事務日誌中。

2)   Sql Server只會記錄外層事務名稱如果企圖回滾任一內層事務,錯誤就會出現。
3)    非常遺憾的是,不管巢狀的事務層次有多深,不帶儲存點的Rollback Tran語句將直接把@@trancount設定為0

解決思路: 1)   採用儲存點:Sql Server提供了一種用於回滾部分事務的機制:Save Tran ,它不會對@@trancount產生任何影響,只是標記回滾事務時可以到達的點。

(但是這種方法不適用於“分散式事務”的遠端呼叫,因為分散式事務不支援事務儲存點: save transaction )
--定義一個是否為巢狀事務的標誌
DECLARE  @nestedFlag BIT
IF(@@trancount>0)
BEGIN
    --是巢狀事務:使用儲存點,避免再次巢狀
    SET  @nestedFlag=1
    SAVE TRAN TestA
END
ELSE
BEGIN
    --不是巢狀事務:開啟一個事務
    SET  @nestedFlag=0
    BEGIN TRAN TestA
END
--執行業務操作,如果出錯就回滾事務點,並立即返回
IF(@@error<>0)
BEGIN
    ROLLBACK TRAN TestA
    RETURN 0
END
--如果不是巢狀事務才提交
IF(@nestedFlag=0)
BEGIN
    COMMIT TRAN TestA
END

2)  如果儲存過程 可能用於分散式事務,先判斷是否被外層事務包括,如果是, 建議不管是否出錯都commit, 然後用RAISERROR丟擲一個異常,並返回一個錯誤碼,由外部事務去判斷是否要回滾。


--可在分散式事務中呼叫的儲存過程樣例.  

create procedure p_trans_test
as
begin
   declare @trancount int;
   set @trancount = @@trancount;

   begin transaction test1
  
   begin try
      print 1-- 業務邏輯
   end try
   begin catch

      if (@trancount = 0)
      begin
         rollback transaction test1;
         return -1
      end
      else
      begin
         commit transaction test1;
         -- 丟擲異常與錯誤碼,由外部呼叫者去處理是否回滾.
         RAISERROR ('Error raised in TRY block.', -- Message text.
               16, -- Severity.
               1 -- State.
               );
         return -1;
      end
   end catch
   return 0;
end