事務失敗會全部回滾嗎?

前言

這個問題看起來是一個簡單明瞭,顯而易見的問題。 事務應該是原子的,要麼整個事務完成,要麼沒有完成。

舉個栗子

CREATE TABLE TestingTransactionRollbacks (
 ID INT NOT NULL PRIMARY KEY ,
 SomeDate DATETIME DEFAULT GETDATE()
 ) ;
GO
BEGIN TRANSACTION
-- succeeds
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (1)
-- Fails. Cannot insert null into a non-null column
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (NULL)
-- succeeds
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
-- fails. Duplicate key
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
-- succeeds
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (3)
COMMIT TRANSACTION
GO
SELECT ID, SomeDate FROM TestingTransactionRollbacks
GO
DROP TABLE TestingTransactionRollbacks

按照通常對事務的理解,如果事務在第一次失敗時,應該回滾,最後的查詢將不返回任何行。 但結果卻不是,它返回3了行。

查詢結果

語句的的失敗被忽略了,==事務完成並提交了==。

如果這是一個重要的業務流程,而不是一個虛構的例子,那麼可能會對數據的事務一致性產生一些不良後果。
這裏究竟發生了什麼? 交易是不是原子? 如果SQL沒有成功完成,SQL是不是應該把它們回滾呢?
微軟官網說明如下:

A transaction is a single unit of work. If a transaction is successful, all of the data modifications made during the transaction are committed and become a permanent part of the database. If a transaction encounters errors and must be canceled or rolled back, then all of the data modifications are erased.

大意如下:
事務是一個單一的工作單位。如果事務成功,則在事務中進行的所有數據修改都將被提交併成爲數據庫的永久部分。如果事務遇到錯誤,必須取消或回退,則所有數據修改都將被清除這表明事實上交易應該自動回滾

不過官網又說了:

If the client’s network connection to an instance of the Database Engine is broken, any outstanding transactions for the connection are rolled back when the network notifies the instance of the break.

If a run-time statement error (such as a constraint violation) occurs in a batch, the default behavior in the Database Engine is to roll back only the statement that generated the error.

如果客戶端到數據庫引擎的實例的網絡連接中斷,則在網絡通知中斷實例時,所有未完成的連接事務將被回滾。

如果批處理中發生==運行時語句錯誤==(例如違反約束),那麼數據庫引擎中的默認行爲是僅回滾生成錯誤的語句。默認行爲是僅回滾生成錯誤的語句。 不是整個交易。

如果連接關閉(網絡錯誤,客戶端斷開,嚴重性錯誤)並且未提交,則事務將回滾。 如果SQL Server終止(關閉,電源故障,意外終止)並且未達到提交,事務將被回滾。 在默認設置下,事務中的語句拋出的非致命錯誤不會自動導致回滾。 (致命=嚴重程度爲19以上)

關於嚴重程度可以參考官網:
數據庫引擎錯誤嚴重性

怎麼辦

那麼,如果我們希望事務在執行過程中遇到任何錯誤時完全回滾,我們該怎麼辦?

有兩個選項。

1)使用Xact_Abort設置

2)捕獲並處理錯誤,並在錯誤處理中指定回滾

Xact_Abort

當SET XACT_ABORT爲ON時,如果Transact-SQL語句引發運行時錯誤,則整個事務將終止並回滾。

當SET XACT_ABORT爲OFF時,在某些情況下,只有導致錯誤的Transact-SQL語句被回滾,事務繼續處理。 根據錯誤的嚴重程度,即使SET XACT_ABORT爲OFF,整個事務也可能會回滾。 默認設置是OFF。

下面做個試驗:

CREATE TABLE TestingTransactionRollbacks (
 ID INT NOT NULL PRIMARY KEY ,
 SomeDate DATETIME DEFAULT GETDATE()
 ) ;
GO
SET XACT_ABORT ON
GO

BEGIN TRANSACTION
-- succeeds
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (1)
-- Fails. Cannot insert null into a non-null column
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (NULL)
-- succeeds
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
-- fails. Duplicate key
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
-- succeeds
INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (3)
COMMIT TRANSACTION
GO
SELECT ID, SomeDate FROM TestingTransactionRollbacks
GO
DROP TABLE TestingTransactionRollbacks

從結果來,第一個運行時錯誤,導致整個事務回滾。返回的結果是0行
# ![這裏寫圖片描述](https://img-blog.csdn.net/20180130133952599?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvejEwODQzMDg3/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

如果你想要的是一旦發生錯誤,事務回滾,但是對任何額外的錯誤處理或日誌記錄不感興趣,開啓這個選項就足夠了。

捕捉錯誤並處理

CREATE TABLE TestingTransactionRollbacks (
 ID INT NOT NULL
 PRIMARY KEY ,
 SomeDate DATETIME DEFAULT GETDATE()
 ) ;
GO

BEGIN TRANSACTION
BEGIN TRY
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (1)
 -- Fails. Cannot insert null into a non-null column
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (NULL)
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
 -- fails. Duplicate key
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (3)
 COMMIT TRANSACTION
END TRY
BEGIN CATCH
 ROLLBACK TRANSACTION
END CATCH
GO
SELECT ID, SomeDate FROM TestingTransactionRollbacks
GO
DROP TABLE TestingTransactionRollbacks

第一個異常將執行轉移到Catch塊中,然後將事務回滾,當select運行時,表中有0行。

記錄錯誤日誌

這看起來像XactAbort一樣,但是處理錯誤有好處的,而不是讓SQL自動將事務處理回滾,自動回滾了可能你就不知道什麼時候發生了什麼錯誤。 catch塊並不侷限於只回滾事務,它可以記錄到錯誤日誌(在回滾之後,這樣日誌不會回滾),它可以採取補償措施,甚至不需要將事務回滾(在多數情況下)。

使用catch塊的原因之一是有許多錯誤相關的函數只在catch塊內調用時才返回數據。這些函數可以創建一個友好的錯誤提示(使用raiserror)。客戶端應用程序得到就不是默認的SQL錯誤消息。而是檢查出現的錯誤是什麼,並針對不同的錯誤採取不同的行爲。

比如像下面的例子:

CREATE TABLE TestingTransactionRollbacks (
 ID INT NOT NULL
 PRIMARY KEY ,
 SomeDate DATETIME DEFAULT GETDATE()
 ) ;
GO

BEGIN TRANSACTION
BEGIN TRY
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (1)
 -- Fails. Cannot insert null into a non-null column
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (NULL)
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
 -- fails. Duplicate key
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (3)
 COMMIT TRANSACTION
END TRY
BEGIN CATCH
  ROLLBACK TRANSACTION
  SELECT  ERROR_NUMBER() AS ErrorNumber, ERROR_SEVERITY() AS Severity, ERROR_MESSAGE() AS ErrorMessage, ERROR_LINE() AS ErrorLine, ERROR_PROCEDURE() AS ErrorProcedure
END CATCH
GO
EXEC InsertWithError

GO
DROP TABLE TestingTransactionRollbacks
DROP PROCEDURE InsertWithError

使用這些函數,可以將確切的錯誤文本記錄到表中以供進一步分析,以及錯誤發生的行和過程,然後將友好錯誤發回給用戶。

doomed transaction

最後需要提到的一件事是doomed transaction的概念。 這是一個事務,一旦執行轉移到catch塊,必須回滾。 最簡單的方法是將XactAbort和Try-Catch塊組合起來

CREATE TABLE TestingTransactionRollbacks (
 ID INT NOT NULL PRIMARY KEY ,
 SomeDate DATETIME DEFAULT GETDATE()
 ) ;
GO

SET XACT_ABORT ON ;

BEGIN TRANSACTION
BEGIN TRY
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (1)
 -- Fails. Cannot insert null into a non-null column
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (NULL)
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
 -- fails. Duplicate key
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (2)
 -- succeeds
 INSERT INTO TestingTransactionRollbacks (ID)
 VALUES (3)
 COMMIT TRANSACTION
END TRY
BEGIN CATCH
 COMMIT TRANSACTION
END CATCH
GO
SELECT ID, SomeDate FROM TestingTransactionRollbacks
GO
DROP TABLE TestingTransactionRollbacks

在這種情況下,我無視錯誤並提交。可能在實際生產系統中很少會出現,但僅用於演示目的。運行這個會返回另一個錯誤(一個在catch塊中拋出)

這裏寫圖片描述

那麼你如何檢查這個? 內置函數XactState會告訴我們事務的狀態。 值爲1表示可以提交事務,值爲-1表示事務已註銷,只能回滾。
用下面的代碼替換catch塊允許代碼運行沒有錯誤

BEGIN CATCH
  IF XACT_STATE() = 1
    COMMIT TRANSACTION
  IF XACT_STATE() = -1
    ROLLBACK TRANSACTION
END CATCH

總結

儘管SQL沒有提供前端應用程序的豐富的異常處理,但它所提供的對於良好的錯誤處理來說是足夠的,特別是與必須提交或作爲原子單位回退的事務相結合。

所有在世界上的錯誤處理,如果沒有被使用,將不會有所幫助,而只是希望代碼每次都能正確運行,從來不是一個好的開發實踐。

在全局開啓XACT_ABORT

EXEC sys.sp_configure N'user options', N'16384'
GO
RECONFIGURE WITH OVERRIDE
GO

直接開啓全局選項的問題,所有問題都被直接回滾了,無法看到錯誤的信息。這不利於對問題的分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章