以下內容根據此官方文檔修改:http://technet.microsoft.com/zh-cn/library/ms189336(v=sql.105).aspx
嵌套事務的使用場景或者說目的主要是為了調用包含了事務的存儲過程。不然沒必要使用嵌套事務。
下列示例顯示了嵌套事務的用途。在TransProc 存儲過程中包含事務,在另外的代碼中分別啟動事務調用TransProc和不啟動事務調用TransProc。
SET QUOTED_IDENTIFIER OFF; GO SET NOCOUNT OFF; GO USE AdventureWorks2008R2; GO CREATE TABLE TestTrans(Cola INT PRIMARY KEY, Colb CHAR(3) NOT NULL); GO CREATE PROCEDURE TransProc @PriKey INT, @CharCol CHAR(3) AS BEGIN TRANSACTION InProc INSERT INTO TestTrans VALUES (@PriKey, @CharCol) INSERT INTO TestTrans VALUES (@PriKey + 1, @CharCol) COMMIT TRANSACTION InProc; GO /* Start a transaction and execute TransProc. */ BEGIN TRANSACTION OutOfProc; GO EXEC TransProc 1, 'aaa'; GO /* Roll back the outer transaction, this will roll back TransProc's nested transaction. */ ROLLBACK TRANSACTION OutOfProc; GO EXECUTE TransProc 3,'bbb'; GO /* The following SELECT statement shows only rows 3 and 4 are still in the table. This indicates that the commit of the inner transaction from the first EXECUTE statement of TransProc was overridden by the subsequent rollback. */ SELECT * FROM TestTrans; GO
嵌套事務有以下特點:
1、SQL Server 數據庫引擎將忽略內部事務的提交,除了將@@TRANCOUNT 減 1。內部事務的真正提交或者回滾是依靠最外部事務結束時進行的提交或者回滾。如果提交外部事務,也將提交內部嵌套事務。如果回滾外部事務,也將回滾所有內部事務,不管是否單獨提交過內部事務。
2、但針對第1條,假如內部事務進行了提交動作(COMMIT TRANSACTION 或 COMMIT WORK),COMMIT只對當前所處的TRANSACTION起作用,也就是說即使COMMIT TRANSACTION transaction_name中的transaction_name是外部事務的名稱,也不會提交該外部事務。這一條的重點在於內部事務無法提交外部事務。
3、@@TRANCOUNT 函數記錄當前事務的嵌套級別,@@TRANCOUNT=0表示不在事務中,等於1表示在一個事務中,大於1表示處在嵌套事務中。每次 BEGIN TRANSACTION 語句使 @@TRANCOUNT 增加 1,每次 COMMIT TRANSACTION 或 COMMIT WORK 語句使 @@TRANCOUNT 減去 1,而只要有一個ROLLBACK就會使@@TRANCOUNT等於0.
4、ROLLBACK TRANSACTION 語句的 transaction_name只能是最外部事務的事務名稱,使用內部事務名稱是非法的。帶最外部事務名稱的ROLLBACK或者不帶任何名稱的ROLLBACK語句都將回滾所有嵌套事務,包括最外部事務。此時@@TRANCOUNT等於0。
5、針對第4條,這里特別需要說明的是:雖然這里說“ROLLBACK語句都將回滾所有嵌套事務,包括最外部事務”,但這里的前提是在最外層進行ROLLBACK,經過本人親自實驗,如果在內部事務中執行帶最外部事務名稱的ROLLBACK或者不帶任何名稱的ROLLBACK,則只回滾當前內部事務和已執行過的外部事務語句,此內部事務后續的外層事務將繼續執行,並能成功修改數據,但后續外部事務中的所有rollback和commit都將不起作用,並提示錯誤信息,因為@@TRANCOUNT早已經等於0,數據庫引擎找不到對應的BEGIN TRANSACTION。可以自己通過下面代碼示例中的bad code示例進行驗證。
以下最佳代碼示范說明參考以下博文:http://www.cnblogs.com/Kymo/archive/2008/05/14/1194161.html
下面用代碼進行解釋,代碼是根據Online Help Commit Transaction一節的代碼修改而成,首先建立一個Table,然后開始三個Trasaction,中間人為觸發一些錯誤,然后觀察運行結果。
--Bad code USE NORTHWIND; --Create test table IF Object_id(N'TestTran',N'U') IS NOT NULL DROP TABLE TESTTRAN; CREATE TABLE TESTTRAN ( COLA INT PRIMARY KEY, COLB CHAR(3)); --Variable for keeping @@ERROR DECLARE @_Error INT; SET @_Error = 0; --Begin 3 nested transaction BEGIN TRANSACTION OUTERTRAN; BEGIN TRANSACTION INNER1; BEGIN TRANSACTION INNER2; INSERT INTO TESTTRAN VALUES (3,'ccc');--Inner2 RAISERROR('Inner2 error', 16, 1) IF @@ERROR = 0 COMMIT TRANSACTION INNER2; ELSE ROLLBACK TRANSACTION ; INSERT INTO TESTTRAN VALUES (2,'bbb');--Inner1 IF @@ERROR = 0 COMMIT TRANSACTION INNER1; ELSE ROLLBACK TRANSACTION ; INSERT INTO TESTTRAN VALUES (1,'aaa');--OuterTran --RAISERROR ('OuterTran error',16,1) IF @@ERROR = 0 COMMIT TRANSACTION OuterTran; ELSE ROLLBACK TRANSACTION; SELECT * FROM TESTTRAN (NOLOCK); SELECT @@Trancount;
上述代碼當內層事務發生錯誤時,並不能正常Rollback,因為Rollback把@@Trancount變成了0,所以后面的Commit語句就找不到對應的Transaction了。解決問題的關鍵就是Rollback時要判斷@@Trancount,當@@Trancount等於1時進行Rollback進行回滾,否則執行Commit把@@Trancount-1,同時把@@Error傳到外層事務交給外層事務處理。微軟的原文是沒有問題的,但是這種情況比較簡單,我們一眼就能看出哪個是內層事務,哪個是外層事務,一共嵌套了幾層,如果是SP調用呢?你不知道你的SP會被誰調用,也不知道會被嵌套幾層。
下面看一下怎么處理內層事務的錯誤(何時Rollback, Commit及錯誤的傳遞)
--Good code USE NORTHWIND; --Create test table IF Object_id(N'TestTran',N'U') IS NOT NULL DROP TABLE TESTTRAN; CREATE TABLE TESTTRAN ( COLA INT PRIMARY KEY, COLB CHAR(3)); --Variable for keeping @@ERROR DECLARE @_Error INT; SET @_Error = 0; --Begin 3 nested transaction BEGIN TRANSACTION OUTERTRAN; BEGIN TRANSACTION INNER1; BEGIN TRANSACTION INNER2; INSERT INTO TESTTRAN VALUES (3,'ccc');--Inner2 --raiserror('Inner2 error', 16, 1) SET @_Error = @@ERRORIF @_Error = 0 COMMIT TRAN INNER2; ELSE IF @@TRANCOUNT > 1 COMMIT TRANSACTION INNER2; ELSE ROLLBACK TRANSACTION INNER2; INSERT INTO TESTTRAN VALUES (2,'bbb');--Inner1 IF @_Error = 0 SET @_Error = @@ERRORIF @_Error = 0 COMMIT TRAN INNER1; ELSE IF @@TRANCOUNT > 1 COMMIT TRANSACTION INNER1; ELSE ROLLBACK TRANSACTION INNER1; INSERT INTO TESTTRAN VALUES (1,'aaa');--OuterTran RAISERROR ('OuterTran error',16,1) -- rollback transaction OuterTran SET @_Error = @_Error + @@ERROR IF @_Error = 0 COMMIT TRAN OUTERTRAN; ELSE IF @@TRANCOUNT > 1 COMMIT TRANSACTION; ELSE ROLLBACK TRANSACTION OUTERTRAN; SELECT * FROM TESTTRAN (NOLOCK)
考慮到SP的調用,我們開發SP時應該在最后把@@ERROR返回供調用者檢查。另外測試注意檢查一下@@Trancount,有時結果看似正確,但是如果@@Trancount不等於0,說明我們的代碼出了問題。
