[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