簡單事務的一個常見例子:把錢從A帳戶轉到B帳戶,這涉及兩項任務,即從A帳戶把錢取出來;把錢存入B帳戶。兩項任務要麼同時成功,要麼一塊兒失敗,給予回滾,以 便保持帳戶的狀態和原來相同。不然,在執行某一個操做的時候可能會由於停電、網絡中斷等緣由而出現故障,因此有可能更新了一個表中的行,但沒有更新相關表 中的行。若是數據庫支持事務,則能夠將數據庫操做組成一個事務,以防止因這些事件而使數據庫出現不一致。html
事務的ACID屬性以下:數據庫
原子性(Atomicity):事務的全部操做是原子工做單元;對於其數據修改,要麼全都執行,要麼全都不執行。原子性消除了系統處理操做子集的可能性。編程
一致性(Consistency):數據從一種正確狀態轉換到另外一種正確狀態。事務在完成時,必須使全部的數據都保持一致。在相關數據庫中,全部規則都必 須應用於事務的修改,以保持全部數據的完整性。當事務結束時,全部的內部數據結構都必須是正確的。在存款取款的例子中,邏輯規則是,錢是不能憑空產生或銷 毀的,對於每一個(收支)條目必須有一個相應的抵衡條目產生,以保證帳戶是平的。安全
隔離性(Isolation):由併發事務所做的修改必須與任何其餘併發事務所做的修改隔離。查看數據時數據所處的狀態,要麼是事務修改它以前的狀態,要 麼是事務修改它以後的狀態。簡單的理解就是,防止多個併發更新彼此干擾。事務在操做數據時與其餘事務操做隔離。隔離性通常是經過加鎖的機制來實現的。性能優化
持久性(Durability):事務完成以後,它對於系統的影響是永久性的。已提交的更改即便在發生故障時也依然存在。服務器
對於事務的開發,.NET平臺也爲咱們提供了幾種很是簡單方便的事務機制。不管是在功能上仍是性能上都提供了優秀的企業級事務支持。網絡
.NET開發者可使用如下5種事務機制:數據結構
l SQL和存儲過程級別的事務。併發
l ADO.NET級別的事務。框架
l ASP.NET頁面級別的事務。
l 企業級服務COM+事務。
l System.Transactions 事務處理。
這5種事務機制有着各自的優點和劣勢,分別表如今性能、代碼數量和部署設置等方面。開發人員能夠根據項目的實際狀況選擇相應的事務機制。
數據庫 事務是其餘事務模型的基礎,當一個事務建立時不一樣數據庫系統都有本身的規則。SQL Server默認在自動提交的模式下工做,每一個語句執行完後都會當即提交;與此對照的是Oracle須要你包含一個提交語句。可是當一個語句經過OLE DB執行時,它執行完後一個提交動做會被附加上去。
例如:
DECLARE @TranName VARCHAR(20); SELECT @TranName = 'MyTransaction'; BEGIN TRANSACTION @TranName; GO USE AdventureWorks; GO DELETE FROM AdventureWorks.HumanResources.JobCandidate WHERE JobCandidateID = 13; GO COMMIT TRANSACTION MyTransaction; GO
或者:
CREATE PROCEDURE Tran1 as begin tran set xact_abort on Insert Into P_Category(CategoryId,Name)values('1','test1') Insert Into P_Category(CategoryId,Name)values('2','test2') commit tran GO
set xact_abort on表示遇到錯誤當即回滾。
固然你也能夠這麼寫:
CREATE PROCEDURE tran1 as begin tran Insert Into P_Category(CategoryId,Name)values('1','test1') if(@@error<>0) rollback tran else begin Insert Into P_Category(CategoryId,Name)values('2','test2') if(@@error<>0) rollback tran else commit tran end GO
數據庫事務有它的優點和限制。
優點:
l 全部的事務邏輯包含在一個單獨的調用中。
l 擁有運行一個事務的最佳性能。
l 獨立於應用程序。
限制:
l 事務上下文僅存在於數據庫調用中。
l 數據庫代碼與數據庫系統有關。
如今咱們對事務的概念和原理都有所瞭解了,而且做爲已經有一些基礎的C#開發者,咱們已經熟知編寫數據庫交互程序的一些要點,即:
(1)使用SqlConnection類的對象的Open()方法創建與數據庫服務器的鏈接。
(2)而後將該鏈接賦給SqlCommand對象的Connection屬性。
(3)將欲執行的SQL語句賦給SqlCommand的CommandText屬性。
(4)經過SqlCommand對象進行數據庫操做。
建立一 個ADO.NET事務是很簡單的,須要定義一個SqlTransaction類型的對象。SqlConnection 和OleDbConnection對象都有一個 BeginTransaction 方法,它能夠返回 SqlTransaction 或者OleDbTransaction 對象。而後賦給SqlCommand對象的Transcation屬性,即實現了兩者的關聯。爲了使事務處理能夠成功完成,必須調用 SqlTransaction對象的Commit()方法。若是有錯誤,則必須調用Rollback()方法撤銷全部的操做。
基於以上認識,下面咱們就開始動手寫一個基於ADO.NET的事務處理程序。
string conString = "data source=127.0.0.1;database=codematic;user id=sa; password="; SqlConnection myConnection = new SqlConnection(conString); myConnection.Open(); //啓動一個事務 SqlTransaction myTrans = myConnection.BeginTransaction(); //爲事務建立一個命令 SqlCommand myCommand = new SqlCommand(); myCommand.Connection = myConnection; myCommand.Transaction = myTrans; try { myCommand.CommandText = "update P_Product set Name='電腦2' where Id=52"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "update P_Product set Name='電腦3' where Id=53"; myCommand.ExecuteNonQuery(); myTrans.Commit();//提交 Response.Write("兩條數據更新成功"); } catch (Exception ex) { myTrans.Rollback();//遇到錯誤,回滾 Response.Write(ex.ToString()); } finally { myConnection.Close(); }
ADO.NET事務的優點和限制以下。
優點:
l 簡單。
l 和數據庫事務差很少快。
l 事務能夠跨越多個數據庫訪問。
l 獨立於數據庫,不一樣數據庫的專有代碼被隱藏了。
限制:事務執行在數據庫鏈接層上,因此須要在執行事務的過程當中手動地維護一個鏈接。
注意:全部命令都必須關聯在同一個鏈接實例上,ADO.NET事務處理不支持跨多個鏈接的事務處理。
頁面聲明Transaction="Required":
<%@ Page Transaction="Required" Language="C#" AutoEventWireup="true" CodeBehind="WebForm3.aspx.cs" Inherits="WebApplication4.WebForm3" %>
頁面引用:using System.EnterpriseServices;。
而後,數據操做代碼:
protected void Button1_Click(object sender, EventArgs e) { try { Work1(); Work2(); ContextUtil.SetComplete(); //提交事務 } catch (System.Exception except) { ContextUtil.SetAbort(); //撤銷事務 Response.Write(except.Message); } } private void Work1() { string conString = "data source=127.0.0.1;database=codematic;user id=sa; password="; SqlConnection myConnection = new SqlConnection(conString); string strSql = "Insert Into P_Category(CategoryId,Name)values('1', 'test1')"; SqlCommand myCommand = new SqlCommand(strSql, myConnection); myConnection.Open(); int rows = myCommand.ExecuteNonQuery(); myConnection.Close(); } private void Work2() { string conString = "data source=127.0.0.1;database=codematic;user id=sa; password="; SqlConnection myConnection = new SqlConnection(conString); string strSql = "Insert Into P_Category(CategoryId,Name)values('2', 'test2')"; SqlCommand myCommand = new SqlCommand(strSql, myConnection); myConnection.Open(); int rows = myCommand.ExecuteNonQuery(); myConnection.Close(); }
ContextUtil是用於獲取 COM+ 上下文信息的首選類。因爲此類的成員所有爲static,所以在使用其成員以前不須要對此類進行實例化。
ASP.NET頁面事務的優點和限制以下。
l 優點:實現簡單,不須要額外的編碼。
l 限制:頁面的全部代碼都是同一個事務,這樣的事務可能會很大,而也許咱們須要的是分開的、小的事務實如今Web層。
.NET Framework 依靠 MTS/COM+ 服務來支持自動事務處理。COM+ 使用 Microsoft Distributed Transaction Coordinator(DTC)做爲事務管理器和事務協調器在分佈式環境中運行事務。這樣可以使 .NET 應用程序運行跨多個資源結合不一樣操做(例如將定單插入SQL Server 數據庫、將消息寫入 Microsoft 消息隊列(MSMQ)隊列,以及從 Oracle 數據庫檢索數據)的事務。
要 實現COM+事務處理的類則必須繼承System.EnterpriseServices.ServicedComponent,這些類須要是公共的,並 且須要提供一個公共的默認的構造器。其實Web Service就是繼承ServicedComponent,因此Web Service也支持COM+事務。要在類定義以前加屬性[Transaction(TransactionOption.Required)]。類裏面 的每一個方法都會運行在一個事務中。
定義一個COM+事務處理的類:
首先引用:using System.EnterpriseServices;
而後繼承:ServicedComponent。
[Transaction(TransactionOption.Required)]
public class OrderData : ServicedComponent
{
}
TransactionOption枚舉類型支持5個值:Disabled、NotSupported、Required、RequiresNew和Supported,如表5-3所示。
表5-3 TransactionOption枚舉類型支持5個值
值 |
說 明 |
Disabled |
忽略當前上下文中的任何事務 |
NotSupported |
使用非受控事務在上下文中建立組件 |
Required |
若是事務存在則共享事務,而且若有必要則建立新事務 |
RequiresNew |
使用新事務建立組件,而與當前上下文的狀態無關 |
Supported |
若是事務存在,則共享該事務 |
通常來講COM+中的組件須要Required 或Supported。當組件用於記錄或查帳時RequiresNew 頗有用,由於組件應該與活動中其餘事務處理的提交或回滾隔離開來。
派生類能夠重載基類的任意屬性。如OrderData選用Required,派生類仍然能夠重載並指定RequiresNew或其餘值。
COM+ 事務有手動處理和自動處理兩種方式,自動處理就是在所須要自動處理的方法前加上[AutoComplete],根據方法的正常或拋出異常決定提交或回滾。 手動處理就是調用ContextUtil類中的EnableCommit、SetComplete和SetAbort方法。
實現步驟以下。
用來建立密鑰的工具是稱爲sn.exe的共享工具。一般經過命令提示運行它,該工具可執行各類任務以生成並提取密鑰。咱們須要用如下方式來運行sn.exe。
sn –k c:\key.snk
其中key.snk 表明將保存密鑰的文件的名稱。它的名稱能夠是任意的,不過習慣上帶有.snk後綴名。
這個文件必須在AssemblyKeyFile屬性中引用,簽名一般是在編譯時進行的。簽名時,用戶可利用C#屬性通知編譯器應該使用正確的密鑰文件對DLL進行簽名。要作到這一點用戶須要打開工程中的AssemblyInfo.cs文件並進行修改。
[assembly:AssemblyKeyFile(「..\\..\\key.snk」)]
注 意:key.snk文件和項目文件在同一個文件夾內。
using System; using System.Data.SqlClient; using System.EnterpriseServices; //企業級服務COM+事務 namespace ClassTran { [Transaction(TransactionOption.Required)] public class OrderData1 : ServicedComponent { //手動事務 public string WorkTran() { try { ContextUtil.EnableCommit(); Work1(); Work2(); ContextUtil.SetComplete(); return "成功!"; } catch (Exception ex) { ContextUtil.SetAbort(); return "失敗!"; } } private void Work1() { string conString = "data source=127.0.0.1;database=codematic; user id=sa;password="; SqlConnection myConnection = new SqlConnection(conString); string strSql = "Insert Into P_Category(CategoryId,Name) values('1','test1')"; SqlCommand myCommand = new SqlCommand(strSql, myConnection); myConnection.Open(); int rows = myCommand.ExecuteNonQuery(); myConnection.Close(); } private void Work2() { string conString = "data source=127.0.0.1;database=codematic; user id=sa;password="; SqlConnection myConnection = new SqlConnection(conString); string strSql = "Insert Into P_Category(CategoryId,Name) values('2','test2')"; SqlCommand myCommand = new SqlCommand(strSql, myConnection); myConnection.Open(); int rows = myCommand.ExecuteNonQuery(); myConnection.Close(); } } }
在方法以前增長屬性[AutoComplete(true)],這樣若是方法執行時沒有異常就默認提交,若是有異常則這個方法就會回滾。
using System; using System.Data.SqlClient; using System.EnterpriseServices;//企業級服務COM+事務 namespace ClassTran { [Transaction(TransactionOption.Required)] public class OrderData2 : ServicedComponent { //自動事務 [AutoComplete(true)] public string WorkTran() { string msg = ""; string conString = "data source=127.0.0.1;database=codematic; user id=sa;password="; SqlConnection myConnection = new SqlConnection(conString); myConnection.Open(); SqlCommand myCommand = new SqlCommand(); myCommand.Connection = myConnection; try { myCommand.CommandText = "update P_Product set Name='電腦2' where Id=52"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "update P_Product set Name='電腦3' where Id=53"; myCommand.ExecuteNonQuery(); msg ="成功!"; } catch (Exception ex) { msg = "失敗:"+ex.Message; } finally { myConnection.Close(); } return msg; } } }
protected void Button1_Click(object sender, EventArgs e) { ClassTran.OrderData1 od1 = new ClassTran.OrderData1(); od1.WorkTran(); } protected void Button2_Click(object sender, EventArgs e) { ClassTran.OrderData2 od2 = new ClassTran.OrderData2(); od2.WorkTran(); }
在須要事務跨 MSMQ 和其餘可識別事務的資源(例如SQL Server 數據庫)運行的系統中,只能使用 DTC 或 COM+ 事務,除此以外沒有其餘選擇。DTC 協調參與分佈式事務的全部資源管理器,也管理與事務相關的操做。
企業級服務COM+事務的前說起優缺點以下。
前提:
l 須要強名字。
l 使用事務的對象須要繼承ServicedComponent。
優點:
l 執行分佈式事務,多個對象能夠輕鬆地運行在同一個事務處理中,事務處理還能夠自動登記。
l 得到COM+服務,諸如對象構建和對象池等。
缺點:
l 因爲存在 DTC 和 COM 互操做性開銷,致使性能下降。
l COM+ 1.0要求每一個事務的隔離級別都設置爲Serializable。
l 使用Enterprise Services的事務老是線程安全的, 也就是說你沒法讓多個線程參與到同一個事務中。
在 .NET Framework 2.0中增長了System.Transactions,這是一種新的命名空間,徹底專一於控制事務性行爲。引入了執行事務性工做的更簡單方法及一些新的 性能優化。System.Transactions提供了一個「輕量級」的、易於使用的Transaction框架。
在上節 中,要實現Transaction須要利用EnterpriseServices,讓組件從ServiceComponent繼承下來。而經過 System.Transactions,則只要簡單的幾行代碼,不須要繼承,不須要Attribute標記。用戶根本不須要考慮是簡單事務仍是分佈式事 務。新模型會自動根據事務中涉及的對象資源判斷使用何種事務管理器。簡而言之,對於任何的事務,用戶只要使用同一種方法進行處理便可。
下面介紹System.Transactions的幾種用法。
首先要引用:using System.Transactions;。
其次,將事務操做代碼放在TransactionScope中執行。如:
using (TransactionScope ts = new TransactionScope())
{
//事務操做代碼
ts.Complete();
}
這 是最簡單,也是最多見的用法。建立了新的 TransactionScope 對象後,即開始建立事務範圍。如代碼示例所示,建議使用 using 語句建立範圍。位於 using 塊內的全部操做將成爲一個事務的一部分,由於它們共享其所定義的事務執行上下文。本例中的最後一行,調用 TransactionScope 的 Complete 方法,將致使退出該塊時請求提交該事務。此方法還提供了內置的錯誤處理,出現異常時會終止事務。
using (TransactionScope ts = new TransactionScope())//使整個代碼塊成爲事務性代碼 { #region 在這裏編寫須要具有Transaction的代碼 string msg = ""; string conString = "data source=127.0.0.1;database=codematic;user id=sa;password="; SqlConnection myConnection = new SqlConnection(conString); myConnection.Open(); SqlCommand myCommand = new SqlCommand(); myCommand.Connection = myConnection; try { myCommand.CommandText = "update P_Product set Name='電腦2' where Id=52"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "update P_Product set Name='電腦3' where Id=53"; myCommand.ExecuteNonQuery(); msg = "成功!"; } catch (Exception ex) { msg = "失敗:" + ex.Message; } finally { myConnection.Close(); } #endregion ts.Complete(); return msg; }
上面的代碼演 示了在一個Transaction Scope裏面打開一個數據庫鏈接的過程。這個數據庫鏈接因爲處在一個Transaction Scope裏面,因此會自動得到Transaction的能力。若是這裏數據庫鏈接的是SQL Server 2005,那麼這個Transaction將不會激活一個MSDTC管理的分佈式事務,而是會由.NET建立一個Local Transaction,性能很是高。可是若是是SQL Server 2000,則會自動激活一個分佈式事務,在性能上會受必定的損失。
再看下面的例子:
void MethodMoreConn() { using (TransactionScope ts = new TransactionScope()) { using (SqlConnection conn = new SqlConnection(conString1)) { conn.Open(); using (SqlConnection conn2 = new SqlConnection(conString2)) { conn2.Open(); } } ts.Complete(); } }
這個例子更加 充分地說明了Transaction Scope的強大,兩個數據庫鏈接!雖然上面的conn和conn2是兩個不一樣的鏈接對象,可能分別鏈接到不一樣的數據庫,可是因爲它們處在一個 TransactionScope中,它們就具有了「聯動」的Transaction能力。在這裏,將自動激活一個MSDTC管理的分佈式事務(能夠經過 打開【管理工具】裏面的組件服務,來查看當前的分佈式事務列表)。
ADO.NET 2.0 中的新增功能支持使用 EnlistTransaction 方法在分佈式事務中登記。因爲 EnlistTransaction 在 Transaction 實例中登記鏈接,所以,該方法利用 System.Transactions 命名空間中的可用功能來管理分佈式事務,從而比使用 System.EnterpriseServices. ITransaction 對象的 EnlistDistributedTransaction 更可取。此外,其語義也稍有不一樣:在一個事務中顯式登記了某個鏈接後,若是第一個事務還沒有完成,則沒法取消登記或在另外一個事務中登記該鏈接。
void MethodEnlist() { CommittableTransaction tx = new CommittableTransaction(); using (SqlConnection conn = new SqlConnection(conString)) { conn.EnlistTransaction(tx); } tx.Commit(); }
void RootMethod() { using (TransactionScope scope = new TransactionScope()) { //操做代碼 SonMethod();//子事務方法 scope.Complete(); } } void SonMethod() { using (TransactionScope scope = new TransactionScope()) { //操做代碼 scope.Complete(); } }
若是你 想要保留代碼部分執行的操做,而且在操做失敗的狀況下不但願停止環境事務,則Suppress對你頗有幫助。例如,在你想要執行日誌記錄或審覈操做時,不 管你的環境事務是提交仍是停止,上述值都頗有用。該值容許你在事務範圍內具備非事務性的代碼部分,如如下示例所示。
void MethodSuppress() { using (TransactionScope scope1 = new TransactionScope())//開始事務 { try { //開始一個非事務範圍 using (TransactionScope scope2 = new TransactionScope( TransactionScopeOption.Suppress)) { //不受事務控制代碼 } //從這裏開始又迴歸事務處理 } catch { } } }
雖然.NET 2.0對事務提供了很好的支持,可是沒有必要老是使用事務。使用事務的第一條規則是,在可以使用事務的時候都應該使用事務,可是不要使用過分。緣由在於, 每次使用事務都會佔用必定的開銷。另外,事務可能會鎖定一些表的行。還有一條規則是,只有當操做須要的時候才使用事務。例如,若是隻是從數據庫中查詢一些 記錄,或者執行單個查詢,則在大部分時候都不須要使用顯式事務。
開發人員應該在頭腦中始終保持一個概念,就是用於修改多個不一樣表數據的冗長事務會嚴重妨礙系統中的全部其餘用戶。這極可能致使一些性能問題。當實現一個事務時,遵循下面的實踐經驗可以達到可接受的結果:
l 避免使用在事務中的Select返回數據,除非語句依賴於返回數據。
l 若是使用Select語句,則只選擇須要的行,這樣不會鎖定過多的資源,而儘量地提升性能。
l 儘可能將事務所有寫在T-SQL或者API中。
l 避免事務與多重獨立的批處理工做結合,應該將這些批處理放置在單獨的事務中。
l 儘量避免大量更新。
另外,必須注意的一點就是事務的默認行爲。在默認狀況下,若是沒有顯式地提交事務,則事務會回滾。雖然默認行爲容許事務的回滾,可是顯式回滾方法老是一個良好的編程習慣。這不只僅只是釋放鎖定數據,也將使得代碼更容易讀取而且更少錯誤。
.NET提供的事務功能很強大,具體的內容遠不止本文所講解的這樣簡單。本文只是起到一個拋磚引玉的功能。但願讀者可以靈活恰當地使用事務功能,而不要過分使用事務,不然可能會對性能起到消極的做用。
原文地址:http://www.cnblogs.com/bicabo/archive/2011/11/14/2248044.html