簡單事務的一個常見例子:把錢從A賬戶轉到B賬戶,這涉及兩項任務,即從A賬戶把錢取出來;把錢存入B賬戶。兩項任務要么同時成功,要么一起失敗,給予回滾,以 便保持賬戶的狀態和原來相同。否則,在執行某一個操作的時候可能會因為停電、網絡中斷等原因而出現故障,所以有可能更新了一個表中的行,但沒有更新相關表 中的行。如果數據庫支持事務,則可以將數據庫操作組成一個事務,以防止因這些事件而使數據庫出現不一致。
事務的ACID屬性如下:
原子性(Atomicity):事務的所有操作是原子工作單元;對於其數據修改,要么全都執行,要么全都不執行。原子性消除了系統處理操作子集的可能性。
一致性(Consistency):數據從一種正確狀態轉換到另一種正確狀態。事務在完成時,必須使所有的數據都保持一致。在相關數據庫中,所有規則都必 須應用於事務的修改,以保持所有數據的完整性。當事務結束時,所有的內部數據結構都必須是正確的。在存款取款的例子中,邏輯規則是,錢是不能憑空產生或銷 毀的,對於每個(收支)條目必須有一個相應的抵衡條目產生,以保證賬戶是平的。
隔離性(Isolation):由並發事務所作的修改必須與任何其他並發事務所作的修改隔離。查看數據時數據所處的狀態,要么是事務修改它之前的狀態,要 么是事務修改它之后的狀態。簡單的理解就是,防止多個並發更新彼此干擾。事務在操作數據時與其他事務操作隔離。隔離性一般是通過加鎖的機制來實現的。
持久性(Durability):事務完成之后,它對於系統的影響是永久性的。已提交的更改即使在發生故障時也依然存在。
對於事務的開發,.NET平台也為我們提供了幾種非常簡單方便的事務機制。無論是在功能上還是性能上都提供了優秀的企業級事務支持。
.NET開發者可以使用以下5種事務機制:
l SQL和存儲過程級別的事務。
l ADO.NET級別的事務。
l ASP.NET頁面級別的事務。
l 企業級服務COM+事務。
l System.Transactions 事務處理。
這5種事務機制有着各自的優勢和劣勢,分別表現在性能、代碼數量和部署設置等方面。開發人員可以根據項目的實際情況選擇相應的事務機制。
5.4.1 SQL和存儲過程級別的事務
數據庫 事務是其他事務模型的基礎,當一個事務創建時不同數據庫系統都有自己的規則。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 數據庫代碼與數據庫系統有關。
ADO.NET級別的事務
現在我們對事務的概念和原理都有所了解了,並且作為已經有一些基礎的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事務處理不支持跨多個連接的事務處理。
ASP.NET 事務
可以說是在.NET平台上事務實現方式最簡單的一種,你僅僅需要一行代碼即可。在aspx的頁面聲明中加一個額外的屬性,即事務屬性 Transaction="Required",它有如下的值:Disabled(默認)、NotSupported、Supported、 Required和RequiresNew,這些設置和COM+及企業級服務中的設置一樣,典型的一個例子是如果你想在頁面上下文中運行事務,那么要將其 設置為Required。如果頁面中包含有用戶控件,那么這些控件也會包含到事務中,事務會存在於頁面的每個地方。5.4.3 ASP.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層。
企業級服務COM+事務
.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方法。
實現步驟如下。
1.給程序添加強名
1)創建一對密鑰
用來創建密鑰的工具是稱為sn.exe的共享工具。通常通過命令提示運行它,該工具可執行各種任務以生成並提取密鑰。我們需要用以下方式來運行sn.exe。
sn –k c:\key.snk
其中key.snk 代表將保存密鑰的文件的名稱。它的名稱可以是任意的,不過習慣上帶有.snk后綴名。
2)簽名
這個文件必須在AssemblyKeyFile屬性中引用,簽名通常是在編譯時進行的。簽名時,用戶可利用C#屬性通知編譯器應該使用正確的密鑰文件對DLL進行簽名。要做到這一點用戶需要打開工程中的AssemblyInfo.cs文件並進行修改。
[assembly:AssemblyKeyFile(“..\\..\\key.snk”)]
注 意:key.snk文件和項目文件在同一個文件夾內。
創建一個項目用以實現事務處理的業務類ClassTran。2.手動事務處理
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(); } } }
3.自動事務處理
在方法之前增加屬性[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; } } }
4.事務方法調用
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的事務總是線程安全的, 也就是說你無法讓多個線程參與到同一個事務中。
System.Transactions 事務處理
在 .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管理的分布式事務(可以通過 打開【管理工具】里面的組件服務,來查看當前的分布式事務列表)。
1.在分布式事務中登記
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(); }
2.實現嵌套事務范圍
void RootMethod() { using (TransactionScope scope = new TransactionScope()) { //操作代碼 SonMethod();//子事務方法 scope.Complete(); } } void SonMethod() { using (TransactionScope scope = new TransactionScope()) { //操作代碼 scope.Complete(); } }
3.事務范圍附加選項
如果你 想要保留代碼部分執行的操作,並且在操作失敗的情況下不希望中止環境事務,則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