什么是数据库事务
数据库事务是指做为单个逻辑工做单元执行的一系列操做。
设想网上购物的一次交易,其付款过程至少包括如下几步数据库操做:
· 更新客户所购商品的库存信息
· 保存客户付款信息--可能包括与银行系统的交互
· 生成订单而且保存到数据库中
· 更新用户相关信息,例如购物数量等等
正常的状况下,这些操做将顺利进行,最终交易成功,与交易相关的全部数据库信息也成功地更新。可是,若是在这一系列过程当中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行账户存款不足等,都将致使交易失败。一旦交易失败,数据库中全部信息都必须保持交易前的状态不变,好比最后一步更新用户信息时失败而致使交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。不然,数据库的信息将会一片混乱而不可预测。
数据库事务正是用来保证这种状况下交易的平稳性和可预测性的技术。
数据库事务的ACID属性
事务处理能够确保除非事务性单元内的全部操做都成功完成,不然不会永久更新面向数据的资源。经过将一组相关操做组合为一个要么所有成功要么所有失败的单元,能够简化错误恢复并使应用程序更加可靠。一个逻辑工做单元要成为事务,必须知足所谓的ACID(原子性、一致性、隔离性和持久性)属性:
· 原子性
事务必须是原子工做单元;对于其数据修改,要么全都执行,要么全都不执行。一般,与某个事务关联的操做具备共同的目标,而且是相互依赖的。若是系统只执行这些操做的一个子集,则可能会破坏事务的整体目标。原子性消除了系统处理操做子集的可能性。
· 一致性
事务在完成时,必须使全部的数据都保持一致状态。在相关数据库中,全部规则都必须应用于事务的修改,以保持全部数据的完整性。事务结束时,全部的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制全部已知的完整性约束。例如,当开发用于转账的应用程序时,应避免在转账过程当中任意移动小数点。
· 隔离性
由并发事务所做的修改必须与任何其它并发事务所做的修改隔离。事务查看数据时数据所处的状态,要么是另外一并发事务修改它以前的状态,要么是另外一事务修改它以后的状态,事务不会查看中间状态的数据。这称为可串行性,由于它可以从新装载起始数据,而且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将得到最高的隔离级别。在此级别上,从一组可并行执行的事务得到的结果与经过连续运行每一个事务所得到的结果相同。因为高度隔离会限制可并行执行的事务数,因此一些应用程序下降隔离级别以换取更大的吞吐量。
· 持久性
事务完成以后,它对于系统的影响是永久性的。该修改即便出现致命的系统故障也将一直保持。
DBMS的责任和咱们的任务
企业级的数据库管理系统(DBMS)都有责任提供一种保证事务的物理完整性的机制。就经常使用的SQL Server2000系统而言,它具有锁定设备隔离事务、记录设备保证事务持久性等机制。所以,咱们没必要关心数据库事务的物理完整性,而应该关注在什么状况下使用数据库事务、事务对性能的影响,如何使用事务等等。
本文将涉及到在.net框架下使用C#语言操纵数据库事务的各个方面。
体验SQL语言的事务机制
做为大型的企业级数据库,SQL Server2000对事务提供了很好的支持。咱们可使用SQL语句来定义、提交以及回滚一个事务。
以下所示的SQL代码定义了一个事务,而且命名为"MyTransaction"(限于篇幅,本文并不讨论如何编写SQL语言程序,请读者自行参考相关书籍):html
DECLARE @TranName VARCHAR(20) SELECT @TranName = 'MyTransaction' BEGIN TRANSACTION @TranNameGOUSE pubs GO UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%' GO COMMIT TRANSACTION MyTransaction GO
这里用到了SQL Server2000自带的示例数据库pubs,提交事务后,将为全部畅销计算机书籍支付的版税增长 10%。
打开SQL Server2000的查询分析器,选择pubs数据库,而后运行这段程序,结果显而易见。
但是如何在C#程序中运行呢?咱们记得在普通的SQL查询中,通常须要把查询语句赋值给SalCommand.CommandText属性,这里也就像普通的SQL查询语句同样,将这些语句赋给SqlCommand.CommandText属性便可。要注意的一点是,其中的"GO"语句标志着SQL批处理的结束,编写SQL脚本是须要的,可是在这里是没必要要的。咱们能够编写以下的程序来验证这个想法:数据库
//TranSql.csusing System; using System.Data; using System.Data.SqlClient; namespace Aspcn { public class DbTranSql { file://将事务放到SQL Server中执行 public void DoTran() { file://创建链接并打开 SqlConnection myConn=GetConn();myConn.Open(); SqlCommand myComm=new SqlCommand(); try { myComm.Connection=myConn; myComm.CommandText="DECLARE @TranName VARCHAR(20) "; myComm.CommandText+="SELECT @TranName = 'MyTransaction' "; myComm.CommandText+="BEGIN TRANSACTION @TranName "; myComm.CommandText+="USE pubs "; myComm.CommandText+="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%' "; myComm.CommandText+="COMMIT TRANSACTION MyTransaction "; myComm.ExecuteNonQuery(); } catch(Exception err) { throw new ApplicationException("事务操做出错,系统信息:"+err.Message); } finally { myConn.Close(); } } file://获取数据链接 private SqlConnection GetConn() { string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password="; SqlConnection myConn=new SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTranSql tranTest=new DbTranSql(); tranTest.DoTran(); Console.WriteLine("事务处理已经成功完成。"); Console.ReadLine(); } } }
注意到其中的SqlCommand对象myComm,它的CommandText属性仅仅是前面SQL代码字符串链接起来便可,固然,其中的"GO"语句已经所有去掉了。这个语句就像普通的查询同样,程序将SQL文本事实上提交给DBMS去处理了,而后接收返回的结果(若是有结果返回的话)。
很天然,咱们最后看到了输出"事务处理已经成功完成",再用企业管理器查看pubs数据库的roysched表,全部title_id字段以"PC"开头的书籍的royalty字段的值都增长了0.1倍。
这里,咱们并无使用ADO.net的事务处理机制,而是简单地将执行事务的SQL语句看成普通的查询来执行,所以,事实上该事务彻底没有用到.net的相关特性。
了解.net中的事务机制
如你所知,在.net框架中主要有两个命名空间(namespace)用于应用程序同数据库系统的交互:System.Data.SqlClient和System.Data.OleDb。前者专门用于链接Microsoft公司本身的SQL Server数据库,然后者能够适应多种不一样的数据库。这两个命名空间中都包含有专门用于管理数据库事务的类,分别是System.Data.SqlClient.SqlTranscation类和System.Data.OleDb.OleDbTranscation类。
就像它们的名字同样,这两个类大部分功能是同样的,两者之间的主要差异在于它们的链接机制,前者提供一组直接调用 SQL Server 的对象,然后者使用本机 OLE DB 启用数据访问。 事实上,ADO.net 事务彻底在数据库的内部处理,且不受 Microsoft 分布式事务处理协调器 (DTC) 或任何其余事务性机制的支持。本文将主要介绍System.Data.SqlClient.SqlTranscation类,下面的段落中,除了特别注明,都将使用System.Data.SqlClient.SqlTranscation类。
事务的开启和提交
如今咱们对事务的概念和原理都了然于心了,而且做为已经有一些基础的C#开发者,咱们已经熟知编写数据库交互程序的一些要点,即便用SqlConnection类的对象的Open()方法创建与数据库服务器的链接,而后将该链接赋给SqlCommand对象的Connection属性,将欲执行的SQL语句赋给它的CommandText属性,因而就能够经过SqlCommand对象进行数据库操做了。对于咱们将要编写的事务处理程序,固然还须要定义一个SqlTransaction类型的对象。而且看到SqlCommand对象的Transcation属性,咱们很容易想到新建的SqlTransaction对象应该与它关联起来。
基于以上认识,下面咱们就开始动手写咱们的第一个事务处理程序。咱们能够很熟练地写出下面这一段程序:
编程
//DoTran.csusing System; using System.Data; using System.Data.SqlClient; namespace Aspcn { public class DbTran { file://执行事务处理 public void DoTran() { file://创建链接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); SqlTransaction myTran=new SqlTransaction(); try { myComm.Connection=myConn; myComm.Transaction=myTran; file://定位到pubs数据库 myComm.CommandText="USE pubs"; myComm.ExecuteNonQuery(); file://更新数据 file://将全部的计算机类图书 myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'"; myComm.ExecuteNonQuery();//提交事务 myTran.Commit(); } catch(Exception err) { throw new ApplicationException("事务操做出错,系统信息:"+err.Message); } finally { myConn.Close(); } } file://获取数据链接 private SqlConnection GetConn() { string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password="; SqlConnection myConn=new SqlConnection(strSql); return myConn; } } public class Test{public static void Main() { DbTran tranTest=new DbTran(); tranTest.DoTran(); Console.WriteLine("事务处理已经成功完成。"); Console.ReadLine(); } } }
显然,这个程序很是简单,咱们很是自信地编译它,可是,出乎意料的结果使咱们的成就感顿时烟消云散:
error CS1501: 重载"SqlTransaction"方法未获取"0"参数
是什么缘由呢?注意到咱们初始化的代码:安全
SqlTransaction myTran=new SqlTransaction();
显然,问题出在这里,事实上,SqlTransaction类并无公共的构造函数,咱们不能这样新建一个SqlTrancaction类型的变量。在事务处理以前确实须要有一个SqlTransaction类型的变量,将该变量关联到SqlCommand类的Transcation属性也是必要的,可是初始化方法却比较特别一点。在初始化SqlTransaction类时,你须要使用SqlConnection类的BeginTranscation()方法:服务器
SqlTransaction myTran; myTran=myConn.BeginTransaction();
该方法返回一个SqlTransaction类型的变量。在调用BeginTransaction()方法之后,全部基于该数据链接对象的SQL语句执行动做都将被认为是事务MyTran的一部分。同时,你也能够在该方法的参数中指定事务隔离级别和事务名称,如:数据结构
SqlTransaction myTran; myTran=myConn.BeginTransaction(IsolationLevel.ReadCommitted,"SampleTransaction");
关于隔离级别的概念咱们将在随后的内容中探讨,在这里咱们只需牢记一个事务是如何被启动,而且关联到特定的数据连接的。
先不要急着去搞懂咱们的事务都干了些什么,看到这一行:并发
myTran.Commit();
是的,这就是事务的提交方式。该语句执行后,事务的全部数据库操做将生效,而且为数据库事务的持久性机制所保持--即便系统在这之后发生致命错误,该事务对数据库的影响也不会消失。
对上面的程序作了修改以后咱们能够获得以下代码(为了节约篇幅,重复之处已省略,请参照前文):框架
//DoTran.cs……} file://执行事务处理 public void DoTran() { file://创建链接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); file://SqlTransaction myTran=new SqlTransaction(); file://注意,SqlTransaction类无公开的构造函数 SqlTransaction myTran; file://建立一个事务 myTran=myConn.BeginTransaction(); try { file://今后开始,基于该链接的数据操做都被认为是事务的一部分 file://下面绑定链接和事务对象 myComm.Connection=myConn; myComm.Transaction=myTran; file://定位到pubs数据库 myComm.CommandText="USE pubs"; myComm.ExecuteNonQuery();//更新数据 file://将全部的计算机类图书 myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'"; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } catch(Exception err) { throw new ApplicationException("事务操做出错,系统信息:"+err.Message); } finally { myConn.Close(); } } //……
到此为止,咱们仅仅掌握了如何开始和提交事务。下一步咱们必须考虑的是在事务中能够干什么和不能够干什么。分布式
另外一个走向极端的错误
满怀信心的新手们可能为本身所掌握的部分知识陶醉不已,刚接触数据库库事务处理的准开发者们也同样,踌躇满志地准备将事务机制应用到他的数据处理程序的每个模块每一条语句中去。的确,事务机制看起来是如此的诱人——简洁、美妙而又实用,我固然想用它来避免一切可能出现的错误——我甚至想用事务把个人数据操做从头至尾包裹起来。
看着吧,下面我要从建立一个数据库开始:函数
using System; using System.Data; using System.Data.SqlClient; namespace Aspcn { public class DbTran { file://执行事务处理 public void DoTran() { file://创建链接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); SqlTransaction myTran; myTran=myConn.BeginTransaction(); file://下面绑定链接和事务对象 myComm.Connection=myConn; myComm.Transaction=myTran; file://试图建立数据库TestDB myComm.CommandText="CREATE database TestDB"; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } file://获取数据链接 private SqlConnection GetConn() { string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password="; SqlConnection myConn=new SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTran tranTest=new DbTran(); tranTest.DoTran(); Console.WriteLine("事务处理已经成功完成。"); Console.ReadLine(); } } } //---------------
未处理的异常: System.Data.SqlClient.SqlException: 在多语句事务内不容许使用 CREATE DATABASE 语句。
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at Aspcn.DbTran.DoTran()
at Aspcn.Test.Main()
注意,以下的SQL语句不容许出如今事务中:
ALTER DATABASE | 修改数据库 |
BACKUP LOG | 备份日志 |
CREATE DATABASE | 建立数据库 |
DISK INIT | 建立数据库或事务日志设备 |
DROP DATABASE | 删除数据库 |
DUMP TRANSACTION | 转储事务日志 |
LOAD DATABASE | 装载数据库备份复本 |
LOAD TRANSACTION | 装载事务日志备份复本 |
RECONFIGURE | 更新使用 sp_configure 系统存储过程更改的配置选项的当前配置(sp_configure 结果集中的 config_value 列)值。 |
RESTORE DATABASE | 还原使用BACKUP命令所做的数据库备份 |
RESTORE LOG | 还原使用BACKUP命令所做的日志备份 |
UPDATE STATISTICS | 在指定的表或索引视图中,对一个或多个统计组(集合)有关键值分发的信息进行更新 |
除了这些语句之外,你能够在你的数据库事务中使用任何合法的SQL语句。
事务回滚
事务的四个特性之一是原子性,其含义是指对于特定操做序列组成的事务,要么所有完成,要么就一件也不作。若是在事务处理的过程当中,发生未知的不可预料的错误,如何保证事务的原子性呢?当事务停止时,必须执行回滚操做,以便消除已经执行的操做对数据库的影响。
通常的状况下,在异常处理中使用回滚动做是比较好的想法。前面,咱们已经获得了一个更新数据库的程序,而且验证了它的正确性,稍微修改一下,能够获得:
//RollBack.cs using System; using System.Data; using System.Data.SqlClient; namespace Aspcn { public class DbTran { file://执行事务处理 public void DoTran() { file://创建链接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); SqlTransaction myTran; file://建立一个事务 myTran=myConn.BeginTransaction(); file://今后开始,基于该链接的数据操做都被认为是事务的一部分 file://下面绑定链接和事务对象 myComm.Connection=myConn; myComm.Transaction=myTran; try { file://定位到pubs数据库 myComm.CommandText="USE pubs"; myComm.ExecuteNonQuery(); myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'"; myComm.ExecuteNonQuery(); file://下面使用建立数据库的语句制造一个错误 myComm.CommandText="Create database testdb"; myComm.ExecuteNonQuery(); myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.20 WHERE title_id LIKE 'Ps%'"; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } catch(Exception err) { myTran.Rollback(); Console.Write("事务操做出错,已回滚。系统信息:"+err.Message); } } file://获取数据链接 private SqlConnection GetConn() { string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password="; SqlConnection myConn=new SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTran tranTest=new DbTran(); tranTest.DoTran(); Console.WriteLine("事务处理已经成功完成。"); Console.ReadLine(); } } }
首先,咱们在中间人为地制造了一个错误——使用前面讲过的Create database语句。而后,在异常处理的catch块中有以下语句:
myTran.Rollback();
当异常发生时,程序执行流跳转到catch块中,首先执行的就是这条语句,它将当前事务回滚。在这段程序能够看出,在Create database以前,已经有了一个更新数据库的操做——将pubs数据库的roysched表中的全部title_id字段以“PC”开头的书籍的royalty字段的值都增长0.1倍。可是,因为异常发生而致使的回滚使得对于数据库来讲什么都没有发生。因而可知,Rollback()方法维护了数据库的一致性及事务的原子性。
使用存储点
事务只是一种最坏状况下的保障措施,事实上,平时系统的运行可靠性都是至关高的,错误不多发生,所以,在每次事务执行以前都检查其有效性显得代价过高——绝大多数的状况下这种耗时的检查是没必要要的。咱们不得不想另一种办法来提升效率。
事务存储点提供了一种机制,用于回滚部分事务。所以,咱们能够没必要在更新以前检查更新的有效性,而是预设一个存储点,在更新以后,若是没有出现错误,就继续执行,不然回滚到更新以前的存储点。存储点的做用就在于此。要注意的是,更新和回滚代价很大,只有在遇到错误的可能性很小,并且预先检查更新的有效性的代价相对很高的状况下,使用存储点才会很是有效。
使用.net框架编程时,你能够很是简单地定义事务存储点和回滚到特定的存储点。下面的语句定义了一个存储点“NoUpdate”:
myTran.Save("NoUpdate");
当你在程序中建立同名的存储点时,新建立的存储点将替代原有的存储点。
在回滚事务时,只需使用Rollback()方法的一个重载函数便可:
myTran.Rollback("NoUpdate");
下面这段程序说明了回滚到存储点的方法和时机:
隔离级别的概念
企业级的数据库每一秒钟均可能应付成千上万的并发访问,于是带来了并发控制的问题。由数据库理论可知,因为并发访问,在不可预料的时刻可能引起以下几个能够预料的问题:
脏读:包含未提交数据的读取。例如,事务1 更改了某行。事务2 在事务1 提交更改以前读取已更改的行。若是事务1 回滚更改,则事务2 便读取了逻辑上从未存在过的行。
不可重复读取:当某个事务不止一次读取同一行,而且一个单独的事务在两次(或屡次)读取之间修改该行时,由于在同一个事务内的屡次读取之间修改了该行,因此每次读取都生成不一样值,从而引起不一致问题。
幻象:经过一个任务,在之前由另外一个还没有提交其事务的任务读取的行的范围中插入新行或删除现有行。带有未提交事务的任务因为该范围中行数的更改而没法重复其原始读取。
如你所想,这些状况发生的根本缘由都是由于在并发访问的时候,没有一个机制避免交叉存取所形成的。而隔离级别的设置,正是为了不这些状况的发生。事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别能够增长并发,但代价是下降数据的正确性。相反,较高的隔离级别能够确保数据的正确性,但可能对并发产生负面影响。
根据隔离级别的不一样,DBMS为并行访问提供不一样的互斥保证。在SQL Server数据库中,提供四种隔离级别:未提交读、提交读、可重复读、可串行读。这四种隔离级别能够不一样程度地保证并发的数据完整性:
隔离级别 | 脏 读 | 不可重复读取 | 幻 像 |
未提交读 | 是 | 是 | 是 |
提交读 | 否 | 是 | 是 |
可重复读 | 否 | 否 | 是 |
可串行读 | 否 | 否 | 否 |
能够看出,“可串行读”提供了最高级别的隔离,这时并发事务的执行结果将与串行执行的彻底一致。如前所述,最高级别的隔离也就意味着最低程度的并发,所以,在此隔离级别下,数据库的服务效率事实上是比较低的。尽管可串行性对于事务确保数据库中的数据在全部时间内的正确性至关重要,然而许多事务并不老是要求彻底的隔离。例如,多个做者工做于同一本书的不一样章节。新章节能够在任意时候提交到项目中。可是,对于已经编辑过的章节,没有编辑人员的批准,做者不能对此章节进行任何更改。这样,尽管有未编辑的新章节,但编辑人员仍能够确保在任意时间该书籍项目的正确性。编辑人员能够查看之前编辑的章节以及最近提交的章节。这样,其它的几种隔离级别也有其存在的意义。
在.net框架中,事务的隔离级别是由枚举System.Data.IsolationLevel所定义的:
[Flags] [Serializable] public enum IsolationLevel
其成员及相应的含义以下:
成 员 | 含 义 |
Chaos | 没法改写隔离级别更高的事务中的挂起的更改。 |
ReadCommitted | 在正在读取数据时保持共享锁,以免脏读,可是在事务结束以前能够更改数据,从而致使不可重复的读取或幻像数据。 |
ReadUncommitted | 能够进行脏读,意思是说,不发布共享锁,也不接受独占锁。 |
RepeatableRead | 在查询中使用的全部数据上放置锁,以防止其余用户更新这些数据。防止不可重复的读取,可是仍能够有幻像行。 |
Serializable | 在DataSet上放置范围锁,以防止在事务完成以前由其余用户更新行或向数据集中插入行。 |
Unspecified | 正在使用与指定隔离级别不一样的隔离级别,可是没法肯定该级别。 |
显而意见,数据库的四个隔离级别在这里都有映射。
默认的状况下,SQL Server使用ReadCommitted(提交读)隔离级别。
关于隔离级别的最后一点就是若是你在事务执行的过程当中改变了隔离级别,那么后面的命名都在最新的隔离级别下执行——隔离级别的改变是当即生效的。有了这一点,你能够在你的事务中更灵活地使用隔离级别从而达到更高的效率和并发安全性。
最后的忠告
无疑,引入事务处理是应对可能出现的数据错误的好方法,可是也应该看到事务处理须要付出的巨大代价——用于存储点、回滚和并发控制所须要的CPU时间和存储空间。
本文的内容只是针对Microsoft SQL Server数据库的,对应于.net框架中的System.Data.SqlClient命名空间,对于使用OleDb的情形,具体的实现稍有不一样,但这不是本文的内容,有兴趣的读者能够到.net中华网(www.aspcn.com)的论坛里找到答案。
using System; using System.Data; using System.Data.SqlClient; namespace Aspcn { public class DbTran { file://执行事务处理 public void DoTran() { file://创建链接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); SqlTransaction myTran; file://建立一个事务 myTran=myConn.BeginTransaction(); file://今后开始,基于该链接的数据操做都被认为是事务的一部分 file://下面绑定链接和事务对象 myComm.Connection=myConn; myComm.Transaction=myTran; try { myComm.CommandText="use pubs"; myComm.ExecuteNonQuery(); myTran.Save("NoUpdate"); myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'"; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } catch(Exception err) { file://更新错误,回滚到指定存储点 myTran.Rollback("NoUpdate"); throw new ApplicationException("事务操做出错,系统信息:"+err.Message); } } file://获取数据链接 private SqlConnection GetConn() { string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password="; SqlConnection myConn=new SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTran tranTest=new DbTran(); tranTest.DoTran(); Console.WriteLine("事务处理已经成功完成。"); Console.ReadLine(); } } }
很明显,在这个程序中,更新无效的概率是很是小的,并且在更新前验证其有效性的代价至关高,所以咱们无须在更新以前验证其有效性,而是结合事务的存储点机制,提供了数据完整性的保证。
原文地址:http://www.cnblogs.com/freeliver54/archive/2008/07/29/1255415.html