巢狀事務的回滾
對於巢狀事務。
1.外部起事務,內部起事務,內外都有Try Catch
內部出錯:如果內部事務出錯,內部和外部事物全部回滾,外部回滾之前的操作全部不存在,但是之後的操作繼續執行。
外部出錯:如果外部事物出錯,內部和外部事物全部回滾,外部回滾之前的操作全部不存在,但是之後的操作繼續執行。
注:如果內部的事務不起事務名稱,內部如果出錯,將會回滾掉會話中的全部事務,而且報異常。
2.外部起事務,內部起事務,內部沒有Try Catch
內部出錯:如果內部事務出錯,內部和外部事物全部回滾,外部回滾之前的操作全部不存在,但是之後的操作繼續執行。
外部出錯:如果內部事務出錯,內部和外部事物全部回滾,外部回滾之前的操作全部不存在,但是之後的操作繼續執行。
3.外部起事務,內部不起事務,但有Try Catch。
內部出錯:外部事物正常提交,外部事物不會進入ROLLBACK,內部出錯之後的記錄也會正常執行。內部操作中,Try部分在錯誤出現之前的操作正常,Try部分在操作之後的操作不執行,然後進入Catch塊中執行操作。
外部出錯:內部和外部事物全部回滾,外部回滾之前的操作全部不存在,但是之後的操作繼續執行。
4.外部起事務,內部不起事務,但沒有Try Catch.
內部出錯:如果內部事務出錯,內部和外部事物全部回滾,外部回滾之前的操作全部不存在,但是之後的操作繼續執行。
外部出錯:如果內部事務出錯,內部和外部事物全部回滾,外部回滾之前的操作全部不存在,但是之後的操作繼續執行。
5.外部不起事務,內部起事務,但有Try Catch.
內部出錯:外部操作被正常執行,內部ROLLBACK操作前全部回滾,之後的操作正常執行。
外部出錯:出錯操作之前的操作不會回滾,出錯之後的操作不執行,跳入Catch塊中,內部事務不會回滾。
6.外部不起事務,內部起事務,但沒有Try Catch.
內部出錯:外部操作被正常執行,內部ROLLBACK操作前全部回滾。由於沒有catch塊,所以外部操作全部執行。
外部出錯:內部事務正常提交,外部只有當條記錄失敗,其他操作正常執行,但是有嚴重錯誤報出來。
對於事務儲存點
事務儲存點只有SAVE和ROLLBACK操作,當外部呼叫內部儲存點,內部出現問題不影響外部事務,外部操作正常執行。當外部操作出現問題時,內部所有操作都回滾掉。
如:外部起事務,內部起儲存點,內外都有Try Catch
內部出錯:外部操作正常,不進入Catch,內部事務回滾到儲存點,之後的繼續執行。
外部出錯:如果外部事物在儲存點之前出現異常,那麼外部和內部所有操作回滾。如果外部事物在儲存點之前出現異常,由於儲存點已經提交了事務,導致外部rollback找不到對應的事務點。
PRINT 'Trancount before transaction: ' + CAST(@@TRANCOUNT as char(1))
BEGIN TRAN
PRINT 'After first BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))
BEGIN TRAN
PRINT 'After second BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))
COMMIT TRAN
PRINT 'After first COMMIT TRAN: ' + CAST(@@TRANCOUNT as char(1))
COMMIT TRAN
PRINT 'After second COMMIT TRAN: ' + CAST(@@TRANCOUNT as char(1))
在結果中,可以看到每一個BEGIN TRAN 語句都會使@@TRANCOUNT增加1並且每一個COMMIT TRAN語句都會使其減少1。如前所述,一個值為0的@@TRANCOUNT意味著沒有開啟的事務。因此,在@@TRANCOUNT值從1降到0時結束的事務發生在外層事務提交的時候。因此,每一個內部事務都需要提交。由於事務起始於第一個BEGIN TRAN並結束於最後一個COMMIT TRAN,因此最外層的事務決定了是否完全提交內部的事務。如果最外層的事務沒有被提交,其中巢狀的事務也不會被提交。
鍵入並執行以下批來檢驗事務回滾時所發生的情況:
BEGIN TRAN
PRINT 'After 1st BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))
BEGIN TRAN
PRINT 'After 2nd BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))
BEGIN TRAN
PRINT 'After 3rd BEGIN TRAN: ' + CAST(@@TRANCOUNT as char(1))
UPDATE Data1
SET value1 = 1000000
WHERE Id = 1
COMMIT TRAN
PRINT 'After first COMMIT TRAN: ' + CAST(@@TRANCOUNT as char(1))
ROLLBACK TRAN
PRINT 'After ROLLBACK TRAN: ' + CAST(@@TRANCOUNT as char(1))
SELECT * FROM Data1
WHERE Id = 1;
在這個示例中,資料表Data1在一個巢狀事務中被更新,這會被立即提交。然後ROLLBACK TRAN被執行。ROLLBACK TRAN將@@TRANCOUNT減為0並回滾整個事務及其中巢狀的事務,無論它們是否已經被提交。因此,巢狀事務中所做的更新被回滾,資料沒有任何改變。
始終牢記,在巢狀的事務中,只有最外層的事務決定著是否提交內部事務。每一個COMMIT TRAN語句總是應用於最後一個執行的BEGIN TRAN。因此,對於每一個COMMIT TRAN,必須呼叫一個COMMIT TRAN來提交事務。ROLLBACK TRAN語句總是屬於最外層的事務,並且因此總是回滾整個事務而不論其中打開了多少巢狀事務。正因為此,管理巢狀事務很複雜。如果每一個巢狀儲存過程都在自身中開始一個事務,那麼巢狀事務大部分會發生在巢狀儲存過程中。要避免巢狀事務,可以在過程開始處檢查@@TRANCOUNT的值,以此來確定是否需要開始一個事務。如果@@TRANCOUNT大於0,因為過程已經處於一個事務中並且呼叫例項可以在錯誤發生時回滾事務。
儲存過程和觸發器中回滾 如果 @@TRANCOUNT 的值在儲存過程完成時與過程執行時不同,則會生成一個 266 資訊類錯誤。該錯誤不是由觸發器中同一個條件生成的。當呼叫儲存過程時,如果 @@TRANCOUNT 為 1 或更大,並且該過程執行 ROLLBACK TRANSACTION 或 ROLLBACK WORK 語句,則會產生 266 號錯誤。這是因為 ROLLBACK 回滾所有未完成的事務,並將 @@TRANCOUNT 減到 0,該值比呼叫過程時要小。
如果在觸發器中發出 ROLLBACK TRANSACTION:
對當前事務中的那一點所做的所有資料修改都將回滾,包括觸發器所做的修改。
觸發器繼續執行 ROLLBACK 語句之後的所有其餘語句。如果這些語句中的任意語句修改資料,則不回滾這些修改。執行其餘的語句不會激發巢狀觸發器。
在批處理中,所有位於激發觸發器的語句之後的語句都不被執行。
觸發器中的 ROLLBACK 關閉並釋放所有在包含激發觸發器的語句的批處理中宣告和開啟的遊標。這其中包括了在激發觸發器的批處理所呼叫的儲存過程中宣告和開啟的遊標。在激發觸發器 的批處理之前的批處理中所宣告的遊標將只是關閉,但是在以下條件下,STATIC 或 INSENSITIVE 遊標不關閉:
CURSOR_CLOSE_ON_COMMIT 設定為OFF。
靜態遊標要麼是同步遊標,要麼是完全填充的非同步遊標。
當執行觸發器時,觸發器的操作總是好像有一個未完成的事務在起作用。如果激發觸發器的語句是在隱性或顯式事務中,則肯定會這樣。在自動提交模式下,也是 如此。當語句開始以自動提交模式執行時,如果遇到錯誤,則會有隱含的 BEGIN TRANSACTION 語句允許恢復該語句生成的所有修改。該隱含的事務對批處理中的其它語句沒有影響,因為當語句完成時,該事務要麼提交,要麼回滾。但是,當呼叫觸發器時,該 隱含的事務將仍然有效。
這意味著,只要觸發器中發出 BEGIN TRANSACTION 語句,則實際上就開始了一個巢狀事務。因為當回滾巢狀事務時,巢狀的 BEGIN TRANSACTION 語句將被忽略,觸發器中發出的 ROLLBACK TRANSACTION 總是回滾過去該觸發器本身發出的所有 BEGIN TRANSACTION 語句。ROLLBACK 回滾到最外部的 BEGIN TRANSACTION。
若要在觸發器中進行部分回滾,則即使總是以自動提交模式進行呼叫,也必須使用 SAVE TRANSACTION 語句。以下的觸發器闡明瞭這一點:
CREATE TRIGGER TestTrig ON TestTab FOR UPDATE AS
SAVE TRANSACTION MyName
INSERT INTO TestAudit
SELECT * FROM inserted
IF (@@error <> 0)
BEGIN
ROLLBACK TRANSACTION MyName
END
這也影響觸發器中 BEGIN TRANSACTION 語句後面的COMMIT TRANSACTION 語句。因為 BEGIN TRANSACTION 啟動一個巢狀事務,而隨後的 COMMIT 語句只應用於該巢狀事務。如果在 COMMIT 之後執行 ROLLBACK TRANSACTION 語句,那麼 ROLLBACK 將一直回滾到最外部的 BEGIN TRANSACTION。以下的觸發器闡明瞭這一點:
CREATE TRIGGER TestTrig ON TestTab FOR UPDATE AS
BEGIN TRANSACTION
INSERT INTO TrigTarget
SELECT * FROM inserted
COMMIT TRANSACTION
ROLLBACK TRANSACTION
此觸發器絕對不會在 TrigTarget 表中插入。BEGIN TRANSACTION 總是啟動一個巢狀事務。COMMIT TRANSACTION 只提交巢狀事務,而下面的 ROLLBACK TRANSACTION 則一直回滾到最外部的 BEGIN TRANSACTION。