简单事务的一个常见例子:把钱从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