SqlServer事務回滾(2)

SQL Server 2008中SQL應用系列--目錄索引sql

SQL事務
 
1、事務概念
    事務是一種機制、是一種操做序列,它包含了一組數據庫操做命令,這組命令要麼所有執行,要麼所有不執行。所以事務是一個不可分割的工做邏輯單元。在數據庫系統上執行併發操做時事務是做爲最小的控制單元來使用的。這特別適用於多用戶同時操做的數據通訊系統。例如:訂票、銀行、保險公司以及證券交易系統等。
 
2、事務屬性
事務4大屬性:
1   原子性(Atomicity):事務是一個完整的操做。
2   一致性(Consistency):當事務完成時,數據必須處於一致狀態。
3   隔離性(Isolation):對數據進行修改的全部併發事務是彼此隔離的。
4   持久性(Durability):事務完成後,它對於系統的影響是永久性的。
 
3、建立事務
T-SQL中管理事務的語句:
1 開始事務: begin transaction
2 提交事務:commit transaction
3 回滾事務: rollback transaction
 
事務分類:
1 顯式事務:用begin transaction明確指定事務的開始。
2 隱性事務:打開隱性事務:set implicit_transactions on,當以隱性事務模式操做時,SQL Servler將在提交或回滾事務後自動啓動新事務。沒法描述事務的開始,只須要提交或回滾事務。
3 自動提交事務:SQL Server的默認模式,它將每條單獨的T-SQL語句視爲一個事務。若是成功執行,則自動提交,不然回滾。數據庫

  在SQL Server 2000中,咱們通常使用RaiseError(http://msdn.microsoft.com/zh-cn/library/ms177497.aspx)來拋出錯誤交給應用程序來處理。看MSDN示例(http://msdn.microsoft.com/zh-cn/library/aa238452%28v=sql.80%29.aspx),自從SQL Server 2005集成Try…Catch功能之後,咱們使用時更加靈活,到了SQL Server 2012,更推出了強大的THROW,處理錯誤顯得更爲精簡。本文對此做一個小小的展現。併發

  首先,咱們假定兩個基本表以下:函數

[sql]  view plain copy print ?
 
  1. --建立兩個測試表  
  2.   
  3. IF NOT OBJECT_ID('Score') IS NULL  
  4. DROP TABLE [Score]  
  5. GO  
  6. IF NOT OBJECT_ID('Student') IS NULL  
  7. DROP TABLE [Student]  
  8. GO  
  9.   
  10. CREATE TABLE Student  
  11. (stuid int NOT NULL PRIMARY KEY,  
  12. stuName Nvarchar(20)  
  13. )  
  14. CREATE TABLE Score  
  15. (stuid int NOT NULL REFERENCES Student(stuid),--外鍵  
  16. scoreValue int  
  17. )  
  18. GO  
  19.   
  20. INSERT INTO Student VALUES (101,'胡一刀')  
  21. INSERT INTO Student VALUES (102,'袁承志')  
  22. INSERT INTO Student VALUES (103,'陳家洛')  
  23. INSERT INTO student VALUES (104,'張三丰')  
  24. GO  
  25.   
  26. SELECT * FROM Student  
  27.   
  28. /*  
  29. stuid stuName  
  30. 101 胡一刀  
  31. 102 袁承志  
  32. 103 陳家洛  
  33. 104 張三丰  
  34. */  



  咱們從一個最簡單的例子入手:測試

例一:ui

 

[sql]  view plain copy print ?
 
  1. /********* 調用運行時錯誤 ***************/  
  2. /********* 3w@live.cn 邀月***************/  
  3. SET XACT_ABORT OFF  
  4. BEGIN TRAN  
  5. INSERT INTO Score VALUES (101,80)  
  6. INSERT INTO Score VALUES (102,87)  
  7. INSERT INTO Score VALUES (107, 59) /* 外鍵錯誤 */  
  8. -----SELECT 1/0 /* 除數爲0錯誤 */  
  9. INSERT INTO Score VALUES (103,100)  
  10. INSERT INTO Score VALUES (104,99)  
  11. COMMIT TRAN  
  12. GO   

 

  先不看結果,我想問一下,該語句執行完畢後,Score表會插入幾條記錄?估計可能有人說是2條,有人說0條,也可能有人說4條。spa

  實際上,我但願是0條,但結果是4條! .net

 

[sql]  view plain copy print ?
 
  1. /*  
  2. (1 row(s) affected)  
  3. (1 row(s) affected)  
  4. Msg 547, Level 16, State 0, Line 5  
  5. The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Score__stuid__01D345B0". The conflict occurred in database "testDb2", table "dbo.Student", column 'stuid'.  
  6. The statement has been terminated.  
  7. (1 row(s) affected)  
  8. (1 row(s) affected)  
  9. */  
  10.   
  11. SELECT * from Score  
  12. /*  
  13. stuid scoreValue  
  14. 101 80  
  15. 102 87  
  16. 103 100  
  17. 104 99  
  18. */  


  我對這個結果也有點驚訝,我但願它出錯回滾,因而修改:日誌

例二:blog

[sql]  view plain copy print ?
 
  1. /********* 調用運行時錯誤 ***************/  
  2. /********* 3w@live.cn 邀月***************/  
  3.   
  4. TRUNCATE table Score  
  5. GO  
  6.   
  7.   
  8. SET XACT_ABORT OFF  
  9. BEGIN TRAN  
  10. INSERT INTO Score VALUES (101,80)  
  11. INSERT INTO Score VALUES (102,87)  
  12. INSERT INTO Score VALUES (107, 59) /* 外鍵錯誤 */  
  13. ----SELECT 1/0  
  14. --INSERT INTO Score VALUES (103,100)  
  15. --INSERT INTO Score VALUES (104,99)  
  16.   
  17. PRINT '@@ERROR是:'+cast(@@ERROR as nvarchar(10))  
  18. IF @@ERROR<>0  
  19. ROLLBACK TRAN  
  20. ELSE  
  21. COMMIT TRAN  
  22. GO   

  我先提示一下你們,這個語句中的@@ERROR值是547,那麼此時,Score表中有幾條記錄?

  答案是2條!

  可能有人開始搖頭了,那麼問題的關鍵在哪兒呢?對,就是這個「XACT_ABORT 」開關,查MSDN(http://msdn.microsoft.com/zh-cn/library/ms188792.aspx),

官方解釋:它用於指定當 Transact-SQL 語句出現運行時錯誤時,SQL Server 是否自動回滾到當前事務。當 SET XACT_ABORT 爲 ON 時,若是執行 Transact-SQL 語句產生運行時錯誤,則整個事務將終止並回滾。當 SET XACT_ABORT 爲 OFF 時,有時只回滾產生錯誤的 Transact-SQL 語句,而事務將繼續進行處理。 若是錯誤很嚴重,那麼即便 SET XACT_ABORT 爲 OFF,也可能回滾整個事務。 OFF 是默認設置。編譯錯誤(如語法錯誤)不受 SET XACT_ABORT 的影響。對於大多數 OLE DB 訪問接口(包括 SQL Server),必須將隱式或顯示事務中的數據修改語句中的 XACT_ABORT 設置爲 ON。 惟一不須要該選項的狀況是在提供程序支持嵌套事務時。

  這裏,紅色的一句話是關鍵,那麼「有時」到底是指何時呢?查資料知:(http://msdn.microsoft.com/zh-cn/library/ms164086.aspx

  大體分爲如下四個級別:

    當等級SEVERITY爲0-10時,爲「信息性消息」,最輕。

    當等級爲11-16時,爲「用戶能夠糾正的數據庫引擎錯誤」。如除數爲零,等級爲16

    當等級爲17-19時,爲「須要DBA注意的錯誤」。如內存不足、數據庫引擎已到極限等。

    當等級爲20-25時,爲「致命錯誤或系統問題」。如硬件或軟件損壞、完整性問題、媒體故障等。

  用戶也能夠自定義錯誤級別和類型。

  根據以上解釋,咱們最保險的方式是:Set XACT_ABORT ON

  固然,使用Try…Catch在Set XACT_ABORT OFF時也能按照咱們的意願回滾。

例三:

[sql]  view plain copy print ?
 
  1. /********* 使用Try Catch 構造一個錯誤記錄 ***************/  
  2. /********* 3w@live.cn 邀月 ***************/  
  3. SET XACT_ABORT OFF  
  4. BEGIN TRY  
  5. BEGIN TRAN  
  6. INSERT INTO Score VALUES (101,80)  
  7. INSERT INTO Score VALUES (102,87)  
  8. INSERT INTO Score VALUES (107, 59) /* 外鍵錯誤 */  
  9. INSERT INTO Score VALUES (103,100)  
  10. INSERT INTO Score VALUES (104,99)  
  11. COMMIT TRAN  
  12. PRINT '事務提交'  
  13. END TRY  
  14. BEGIN CATCH  
  15. ROLLBACK  
  16. PRINT '事務回滾' --構造一個錯誤信息記錄  
  17.   
  18. SELECT ERROR_NUMBER() AS 錯誤號,  
  19. ERROR_SEVERITY() AS 錯誤等級,  
  20. ERROR_STATE() as 錯誤狀態,  
  21. DB_ID() as 數據庫ID,  
  22. DB_NAME() as 數據庫名稱,  
  23. ERROR_MESSAGE() as 錯誤信息;  
  24. END CATCH  
  25. GO  

  這個返回結果比較另類,它實際上是一條拼湊起來的記錄。

  記錄並無新增,由於Catch到錯誤而事務回滾了。

  使用RaiseError也能夠把出錯的信息拋給應用程序來處理。

例四:

[sql]  view plain copy print ?
 
  1. /********* 使用RaiseError 提交一個錯誤信息***************/  
  2. /********* 3w@live.cn 邀月 ***************/  
  3. SET XACT_ABORT OFF  
  4. BEGIN TRY  
  5. BEGIN TRAN  
  6. INSERT INTO Score VALUES (101,80)  
  7. INSERT INTO Score VALUES (102,87)  
  8. INSERT INTO Score VALUES (107, 59) /* 外鍵錯誤 */  
  9. INSERT INTO Score VALUES (103,100)  
  10. INSERT INTO Score VALUES (104,99)  
  11. COMMIT TRAN  
  12. PRINT '事務提交'  
  13. END TRY  
  14. BEGIN CATCH  
  15. ROLLBACK  
  16. PRINT '事務回滾';--構造一個錯誤信息記錄  
  17.   
  18. DECLARE @ErrorMessage NVARCHAR(4000);  
  19. DECLARE @ErrorSeverity INT;  
  20. DECLARE @ErrorState INT;  
  21.   
  22. SELECT @ErrorMessage = ERROR_MESSAGE(),  
  23. @ErrorSeverity = ERROR_SEVERITY(),  
  24. @ErrorState = ERROR_STATE();  
  25.   
  26. RAISERROR (@ErrorMessage, -- Message text.  
  27. @ErrorSeverity, -- Severity.  
  28. @ErrorState -- State.  
  29. );  
  30. END CATCH  
  31. GO   

  或者直接使用Throw也能達到RaiseError一樣的效果,並且這是微軟推崇的方式:其官方解釋爲「THROW 語句支持 SET XACT_ABORT,但 RAISERROR 不支持。 新應用程序應該改用 THROW,而不使用 RAISERROR。」其實,多是微軟在忽悠,由於,其實RaiseError也支持Set XACT_ABORT。

例五:

 

[sql]  view plain copy print ?
 
  1. /********* SQL 2012新增的Throw ***************/  
  2. /********* 3w@live.cn 邀月***************/  
  3. SET XACT_ABORT OFF  
  4. BEGIN TRY  
  5. BEGIN TRAN  
  6. INSERT INTO score VALUES (101,80)  
  7. INSERT INTO score VALUES (102,87)  
  8. INSERT INTO score VALUES (107, 59) /* 外鍵錯誤 */  
  9. INSERT INTO score VALUES (103,100)  
  10. INSERT INTO score VALUES (104,99)  
  11. COMMIT TRAN  
  12. PRINT '事務提交'  
  13. END TRY  
  14. BEGIN CATCH  
  15. ROLLBACK;  
  16. PRINT '事務回滾';  
  17. Throw;  
  18. END CATCH  
  19. GO  

  不過,說實話,Throw好像很簡練。

  說到這裏,我有一個疑問:例四和例五的查詢結果相同:

[sql]  view plain copy print ?
 
  1. /*  
  2. (1 row(s) affected)  
  3. (1 row(s) affected)  
  4. (0 row(s) affected)  
  5. 事務回滾  
  6. Msg 547, Level 16, State 0, Line 13  
  7. The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Score__stuid__18B6AB08". The conflict occurred in database "testDb2", table "dbo.Student", column 'stuid'.  
  8. */  

  雖然由於回滾而沒有插入數據,可是兩個「(1 row(s) affected) 」仍是讓我吃了一驚,哪位高手能告訴我一下,這影響的兩行SQL Server到底是怎麼處理的?先謝過了。

  既然,錯誤已經被捕獲,那麼有兩種處理方式,一是直接在數據庫中記錄到表中。好比:咱們能夠創建一個數據庫DBErrorLogs,

[sql]  view plain copy print ?
 
  1. /********* 生成錯誤日誌記錄表 ******/  
  2.   
  3. /********* 3w@live.cn 邀月***************/  
  4. CREATE database DBErrorLogs  
  5. GO  
  6.   
  7. USE DBErrorLogs  
  8. GO  
  9.   
  10. CREATE TABLE [dbo].[ErrorLog](  
  11. [nId] [bigint] IDENTITY(101,1) NOT NULL PRIMARY KEY,  
  12. [dtDate] [datetime] NOT NULL,  
  13. [sThread] [varchar](100) NOT NULL,  
  14. [sLevel] [varchar](200) NOT NULL,  
  15. [sLogger] [varchar](500) NOT NULL,  
  16. [sMessage] [varchar](3000) NOT NULL,  
  17. [sException] [varchar](4000) NULL  
  18. )  
  19. GO  
  20.   
  21. ALTER TABLE [dbo].[ErrorLog] ADD DEFAULT (getdate()) FOR [dtDate]  
  22. GO  

  在出錯時直接插入相應信息到該表中便可。另一種思路是交給應用程序來處理,好比下例中,咱們用C#捕獲錯誤,並用log4net記錄回數據庫中。C#中有相應的SQLException類,封裝了相應的Error的等級、編號、出錯信息等,真心方便。

[csharp]  view plain copy print ?
 
  1. using System;  
  2. using System.Text;  
  3. using System.Data.SqlClient;  
  4. using System.Data;  
  5.   
  6. namespace RaiseErrorDemo_Csharp  
  7. {  
  8. public class Program  
  9. {  
  10. #region Define Members  
  11. private static log4net.ILog myLogger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);  
  12. static string conn = "Data Source=AP4\\Net2012;Initial Catalog=Testdb2;Integrated Security=True";  
  13. static string sql_RaiseError = @"  
  14. /********* 使用RaiseError 提交一個錯誤信息***************/  
  15. /********* 3w@live.cn 邀月 ***************/  
  16. SET XACT_ABORT OFF  
  17. BEGIN TRY  
  18. BEGIN TRAN  
  19. INSERT INTO Score VALUES (101,80)  
  20. INSERT INTO Score VALUES (102,87)  
  21. INSERT INTO Score VALUES (107, 59) /* 外鍵錯誤 */  
  22. INSERT INTO Score VALUES (103,100)  
  23. INSERT INTO Score VALUES (104,99)  
  24. COMMIT TRAN  
  25. PRINT '事務提交'  
  26. END TRY  
  27. BEGIN CATCH  
  28. ROLLBACK  
  29. PRINT '事務回滾';--構造一個錯誤信息記錄  
  30. DECLARE @ErrorMessage NVARCHAR(4000);  
  31. DECLARE @ErrorSeverity INT;  
  32. DECLARE @ErrorState INT;  
  33.   
  34. SELECT @ErrorMessage = ERROR_MESSAGE(),  
  35. @ErrorSeverity = ERROR_SEVERITY(),  
  36. @ErrorState = ERROR_STATE();  
  37.   
  38. RAISERROR (@ErrorMessage, -- Message text.  
  39. @ErrorSeverity, -- Severity.  
  40. @ErrorState -- State.  
  41. );  
  42. END CATCH  
  43. ";  
  44. static string sql_Throw = @"  
  45. SET XACT_ABORT OFF  
  46. BEGIN TRY  
  47. BEGIN TRAN  
  48. INSERT INTO score VALUES (101,80)  
  49. INSERT INTO score VALUES (102,87)  
  50. INSERT INTO score VALUES (107, 59) /* 外鍵錯誤 */  
  51. INSERT INTO score VALUES (103,100)  
  52. INSERT INTO score VALUES (104,99)  
  53. COMMIT TRAN  
  54. PRINT '事務提交'  
  55. END TRY  
  56. BEGIN CATCH  
  57. ROLLBACK;  
  58. PRINT '事務回滾';  
  59. Throw;  
  60. END CATCH  
  61. ";  
  62. #endregion  
  63.  
  64. #region Methods  
  65.   
  66. /// <summary>  
  67. /// 主函數  
  68. /// </summary>  
  69. /// <param name="args"></param>  
  70. static void Main(string[] args)  
  71. {  
  72. CatchSQLError(sql_RaiseError);  
  73. Console.WriteLine("-----------------------------------------------");  
  74. CatchSQLError(sql_Throw);  
  75. Console.ReadKey();  
  76. }  
  77.   
  78. /// <summary>  
  79. /// 捕獲錯誤信息  
  80. /// </summary>  
  81. /// <param name="strSQL"></param>  
  82.   
  83. public static void CatchSQLError(string strSQL)  
  84. {  
  85. string connectionString = conn;  
  86. SqlConnection connection = new SqlConnection(connectionString);  
  87. SqlCommand cmd2 = new SqlCommand(strSQL, connection);  
  88. cmd2.CommandType = CommandType.Text;  
  89. try  
  90. {  
  91. connection.Open();  
  92. cmd2.ExecuteNonQuery();  
  93. }  
  94. catch (SqlException err)  
  95. {  
  96. string strErr = GetPreError(err.Class);  
  97. //顯示出錯信息  
  98. Console.WriteLine("錯誤等級:" + err.Class + Environment.NewLine + strErr + err.Message);  
  99. //記錄錯誤到數據庫中  
  100. myLogger.Error(strErr, err);  
  101. }  
  102. finally  
  103. {  
  104. connection.Close();  
  105. }  
  106. }  
  107. /// <summary>  
  108. /// 輔助函數  
  109. /// </summary>  
  110. /// <param name="b"></param>  
  111. /// <returns></returns>  
  112. public static string GetPreError(byte b)  
  113. {  
  114. string strErr = string.Empty;  
  115. if (b >= 0 && b <= 10)  
  116. {  
  117. strErr = "信息性信息:";  
  118. }  
  119. else if (b >= 11 && b <= 16)  
  120. {  
  121. strErr = "用戶能夠糾正的數據庫引擎錯誤:";  
  122. }  
  123. else if (b >= 17 && b <= 19)  
  124. {  
  125. strErr = "須要DBA注意的錯誤:";  
  126. }  
  127. else if (b >= 20 && b <= 25)  
  128. {  
  129. strErr = "致命錯誤或系統問題:";  
  130. }  
  131. else  
  132. {  
  133. strErr = "地球要毀滅了,快跑啊:";  
  134. }  
  135. return strErr;  
  136. }  
  137.  
  138. #endregion  
  139.   
  140. }  
  141. }  

文後附有C#源碼。執行效果:

 

小結:

一、SQL Server處理錯誤時有一個重要的開關XACT_ABORT,沒事的時候,記得把它打開。

二、SQL Server提供的錯誤信息很豐富,請區分等級採起相應的對策,固然,還能夠本身增長更爲實用貼切的自定義錯誤類型。

 

下載源碼

相關文章
相關標籤/搜索