1. 程式人生 > 實用技巧 >The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION

The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION

如果你的儲存過程或其他指令碼出現下面這個錯誤,一般是因為ROLLBACK TRANSACTION在邏輯上缺少匹配的BEGIN TRANSACTION或者沒有開始一個事務(也有可能此事務已經提交),但是你做了事務回滾操作(ROLLBACK TRANSACTION),否則就可能出現這種錯誤。

Msg 3903, Level 16, State 1, Line 22

The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.

出現這種錯誤有很多種可能性,下面我們來通過一些案例來簡單介紹一下這個錯誤,這些案例都是一些特殊案例的簡化版本。

案例1:

CREATEPROCEDURE PRC_EXC
AS
BEGIN
 SELECT 1/0 --僅僅模擬儲存過程出現異常。
END;
CREATEPROCEDURE PRC_TEST
AS
BEGIN
 BEGIN TRY
 BEGINTRAN TT
 UPDATE dbo.TEST SET NAME='k3'WHERE object_id =9;
 COMMITTRAN TT;
 EXEC dbo.PRC_EXC
 END TRY
 BEGIN CATCH
 ROLLBACKTRAN TT;
 END CATCH
END

如果你執行儲存過程PRC_TEST,如下所示,因為執行儲存過程dbo.PRC_EXC時遇到異常被捕獲,此時在BEGIN CATCH部分執行ROLLBACK TRAN TT,但是實際上,此事務已經提交,資料庫根本沒有這樣一個事務,然後你又要回滾事務,所以出錯。可能讓人好奇的是為什麼儲存過程dbo.PRC_EXC不放在事務裡面,這裡僅僅是簡單模擬生產環境的一個案例,正確的做法應該將dbo.PRC_EXC放入事務當中,或者將dbo.PRC_EXC放入另外一個BEGIN TRY ... END TRY裡面去。

如果要在捕獲一個事務裡面出現異常的正確的做法如下所示,個人更傾向於第二種寫法。

BEGINTRANSACTION;
BEGIN TRY
 ...................
 ...................
 --執行所有業務邏輯後,最後提交
 COMMIT;
END TRY
BEGIN CATCH
 --if an exception occurs execute your rollback, also test that you have had some successful transactions
 IF @@TRANCOUNT > 0 ROLLBACK; 
END CATCH
BEGIN TRY
 BEGINTRANSACTION;
 ....................
 ....................
 --執行所有業務邏輯後,最後提交
 COMMIT;
END TRY
BEGIN CATCH
 --if an exception occurs execute your rollback, also test that you have had some successful transactions
 IF @@TRANCOUNT > 0 ROLLBACK; 
END CATCH

案例2:

CREATEPROCEDURE PRC_TEST2
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGINTRANSACTION
 UPDATE dbo.TEST SET NAME='k3'WHERE object_id =9; --這裡用簡單的UPDATE替換複雜的業務邏輯。
 IF @@Error != 0 GOTO ERROR_HANDLER
 UPDATE dbo.TEST SET NAME='k3'WHERE object_id =15; --這裡用簡單的UPDATE替換複雜的業務邏輯。
 
 IF @@Error != 0 GOTO ERROR_HANDLER
COMMITTRANSACTION
 
ERROR_HANDLER: ROLLBACKTRANSACTION
SET NOCOUNT OFF
RETURN 0
GO

上面錯誤的原因,在於沒有異常或錯誤時,事務提交後,這一句ERROR_HANDLER:ROLLBACKTRANSACTION總是會被執行,邏輯上已經沒有事務了。所以正確的做法,事務提交後,直接RETURN,避免正常情況下執行ERROR_HANDLER: ROLLBACK TRANSACTION,或者將回滾邏輯放到IF條件之後,不要用GOTO這種寫法.

正確的SQL:

ALTERPROCEDURE PRC_TEST2
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGINTRANSACTION
 UPDATE dbo.TEST SET NAME='k3'WHERE object_id =9; --這裡用簡單的UPDATE替換複雜的業務邏輯。
 IF @@Error != 0 GOTO ERROR_HANDLER
 UPDATE dbo.TEST SET NAME='k3'WHERE object_id =15; --這裡用簡單的UPDATE替換複雜的業務邏輯。
IF @@Error != 0 GOTO ERROR_HANDLER
COMMITTRANSACTION
SET NOCOUNT OFF
RETURN 0;
 
ERROR_HANDLER: ROLLBACKTRANSACTION
SET NOCOUNT OFF
RETURN 0
GO

這裡來一個簡單的演示,你可以體會一下,所謂的BEGIN TRAN與ROLLBACK TRANSACTION並不是指數量匹對,而是邏輯上事務回滾前,必須有一個未提交的事務。

SELECT * INTO test FROM sys.objects
SELECT @@TRANCOUNT;--值為0
BEGINTRAN
UPDATE TEST SET name = 'kkk'WHERE object_id =7;
SELECT @@TRANCOUNT;--值為1,
COMMITTRAN
ROLLBACKTRAN; --事務其實已經結束,突然來一個回滾事務,沒有匹配的BEGIN TRAN,所以出現報錯"The ROLLBACKTRANSACTION request has nocorrespondingBEGINTRANSACTION."

案例3:

下面這種錯誤,純屬菜鳥級別犯的錯誤或粗心大意所致。

CREATEPROCEDURE PRC_TEST4
AS
SET NOCOUNT ON
BEGIN
BEGIN TRY
 UPDATE dbo.TEST SET NAME='k3'WHERE object_id =9; --這裡用簡單的UPDATE替換複雜的業務邏輯。
 COMMITTRANSACTION;
END TRY
BEGIN CATCH
 ROLLBACKTRANSACTION
END CATCH
END;

總結:

實際案例中,如果儲存過程裡面有複雜的業務邏輯,尤其出現巢狀呼叫儲存過程的時候,特別是多層巢狀時,這種問題排查起來也相當麻煩。所以儘量少用巢狀呼叫儲存過程。簡化業務邏輯!另外,出現這種錯誤時,需要仔細檢查程式碼邏輯才能找出這些出錯的地方,似乎也沒有其它更好的方法。