SQL语言的事务机制_转摘

什么是数据库事务

  数据库事务是指作为单个逻辑工作单元执行的一系列操作。

设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:

  · 更新客户所购商品的库存信息

  · 保存客户付款信息--可能包括与银行系统的交互

  · 生成订单并且保存到数据库中

  · 更新用户相关信息,例如购物数量等等

正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。

数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。

  数据库事务的ACID属性

事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性:

  · 原子性

事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。

  · 一致性

事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。

  · 隔离性

由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。

  · 持久性

 事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
DBMS的责任和我们的任务

企业级的数据库管理系统(DBMS)都有责任提供一种保证事务的物理完整性的机制。就常用的SQL Server2000系统而言,它具备锁定设备隔离事务、记录设备保证事务持久性等机制。因此,我们不必关心数据库事务的物理完整性,而应该关注在什么情况下使用数据库事务、事务对性能的影响,如何使用事务等等。

本文将涉及到在.net框架下使用C#语言操纵数据库事务的各个方面。

 

体验SQL语言的事务机制

作为大型的企业级数据库,SQL Server2000对事务提供了很好的支持。我们可以使用SQL语句来定义、提交以及回滚一个事务。

如下所示的SQL代码定义了一个事务,并且命名为"MyTransaction"(限于篇幅,本文并不讨论如何编写SQL语言程序,请读者自行参考相关书籍):

None.gifDECLARE @TranName VARCHAR(20)
None.gif
None.gif
SELECT @TranName = 'MyTransaction' 
None.gif
BEGIN TRANSACTION @TranNameGOUSE pubs
None.gif
GO
None.gif
None.gif
UPDATE roysched
None.gif
SET royalty = royalty * 1.10
None.gif
WHERE title_id LIKE 'Pc%'
None.gif
GO
None.gif
None.gif
COMMIT TRANSACTION MyTransaction
None.gif
GO
None.gif

        这里用到了SQL Server2000自带的示例数据库pubs,提交事务后,将为所有畅销计算机书籍支付的版税增加 10%。

打开SQL Server2000的查询分析器,选择pubs数据库,然后运行这段程序,结果显而易见。

可是如何在C#程序中运行呢?我们记得在普通的SQL查询中,一般需要把查询语句赋值给SalCommand.CommandText属性,这里也就像普通的SQL查询语句一样,将这些语句赋给SqlCommand.CommandText属性即可。要注意的一点是,其中的"GO"语句标志着SQL批处理的结束,编写SQL脚本是需要的,但是在这里是不必要的。我们可以编写如下的程序来验证这个想法:

None.gif//TranSql.csusing System;
None.gif
using System.Data;
None.gif
using System.Data.SqlClient;
None.gif
namespace Aspcn
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif 
public class DbTranSql
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  file:
//将事务放到SQL Server中执行
InBlock.gif
  public void DoTran()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   file:
//建立连接并打开
InBlock.gif
   SqlConnection myConn=GetConn();myConn.Open();
InBlock.gif   SqlCommand myComm
=new SqlCommand();
InBlock.gif   
try
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    myComm.Connection
=myConn;
InBlock.gif    myComm.CommandText
="DECLARE @TranName VARCHAR(20) ";
InBlock.gif    myComm.CommandText
+="SELECT @TranName = 'MyTransaction' ";
InBlock.gif    myComm.CommandText
+="BEGIN TRANSACTION @TranName ";
InBlock.gif    myComm.CommandText
+="USE pubs ";
InBlock.gif    myComm.CommandText
+="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%' ";
InBlock.gif    myComm.CommandText
+="COMMIT TRANSACTION MyTransaction ";
InBlock.gif    myComm.ExecuteNonQuery();
ExpandedSubBlockEnd.gif   }

InBlock.gif   
catch(Exception err)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    
throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
ExpandedSubBlockEnd.gif   }

InBlock.gif   
finally
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    myConn.Close();
ExpandedSubBlockEnd.gif   }

ExpandedSubBlockEnd.gif  }

InBlock.gif  file:
//获取数据连接
InBlock.gif
  private SqlConnection GetConn()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   
string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
InBlock.gif   SqlConnection myConn
=new SqlConnection(strSql);
InBlock.gif   
return myConn;
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif 
public class Test
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   DbTranSql tranTest
=new DbTranSql();
InBlock.gif   tranTest.DoTran();
InBlock.gif   Console.WriteLine(
"事务处理已经成功完成。");
InBlock.gif   Console.ReadLine();
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

None.gif


  注意到其中的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对象应该与它关联起来。

基于以上认识,下面我们就开始动手写我们的第一个事务处理程序。我们可以很熟练地写出下面这一段程序:

None.gif//DoTran.csusing System;
None.gif
using System.Data;
None.gif
using System.Data.SqlClient;
None.gif
namespace Aspcn
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif 
public class DbTran
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  file:
//执行事务处理
InBlock.gif
  public void DoTran()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   file:
//建立连接并打开
InBlock.gif
   SqlConnection myConn=GetConn();
InBlock.gif   myConn.Open();
InBlock.gif   SqlCommand myComm
=new SqlCommand();
InBlock.gif   SqlTransaction myTran
=new SqlTransaction();
InBlock.gif   
try
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    myComm.Connection
=myConn;
InBlock.gif    myComm.Transaction
=myTran;
InBlock.gif   
InBlock.gif    file:
//定位到pubs数据库 
InBlock.gif
    myComm.CommandText="USE pubs";
InBlock.gif    myComm.ExecuteNonQuery();
InBlock.gif
InBlock.gif    file:
//更新数据
InBlock.gif
    file://将所有的计算机类图书
InBlock.gif
    myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
InBlock.gif    myComm.ExecuteNonQuery();
//提交事务
InBlock.gif
    myTran.Commit();
ExpandedSubBlockEnd.gif   }

InBlock.gif   
catch(Exception err)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    
throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
ExpandedSubBlockEnd.gif   }

InBlock.gif   
finally
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    myConn.Close();
ExpandedSubBlockEnd.gif   }

ExpandedSubBlockEnd.gif  }

InBlock.gif  file:
//获取数据连接
InBlock.gif
  private SqlConnection GetConn()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   
string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
InBlock.gif   SqlConnection myConn
=new SqlConnection(strSql);
InBlock.gif   
return myConn;
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public class Testdot.gif{public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  DbTran tranTest
=new DbTran();
InBlock.gif  tranTest.DoTran();
InBlock.gif  Console.WriteLine(
"事务处理已经成功完成。");
InBlock.gif  Console.ReadLine();
ExpandedSubBlockEnd.gif }

ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif

显然,这个程序非常简单,我们非常自信地编译它,但是,出乎意料的结果使我们的成就感顿时烟消云散:

error CS1501: 重载"SqlTransaction"方法未获取"0"参数

是什么原因呢?注意到我们初始化的代码:

None.gifSqlTransaction myTran=new SqlTransaction();
None.gif

显然,问题出在这里,事实上,SqlTransaction类并没有公共的构造函数,我们不能这样新建一个SqlTrancaction类型的变量。在事务处理之前确实需要有一个SqlTransaction类型的变量,将该变量关联到SqlCommand类的Transcation属性也是必要的,但是初始化方法却比较特别一点。在初始化SqlTransaction类时,你需要使用SqlConnection类的BeginTranscation()方法:

None.gifSqlTransaction myTran; 
   myTran
=myConn.BeginTransaction();
None.gif

该方法返回一个SqlTransaction类型的变量。在调用BeginTransaction()方法以后,所有基于该数据连接对象的SQL语句执行动作都将被认为是事务MyTran的一部分。同时,你也可以在该方法的参数中指定事务隔离级别和事务名称,如:

None.gifSqlTransaction myTran; 
None.gifmyTran
=myConn.BeginTransaction(IsolationLevel.ReadCommitted,"SampleTransaction"); 
None.gif

   关于隔离级别的概念我们将在随后的内容中探讨,在这里我们只需牢记一个事务是如何被启动,并且关联到特定的数据链接的。

先不要急着去搞懂我们的事务都干了些什么,看到这一行:

None.gifmyTran.Commit();
None.gif

   是的,这就是事务的提交方式。该语句执行后,事务的所有数据库操作将生效,并且为数据库事务的持久性机制所保持--即使系统在这以后发生致命错误,该事务对数据库的影响也不会消失。

对上面的程序做了修改之后我们可以得到如下代码(为了节约篇幅,重复之处已省略,请参照前文):

None.gif//DoTran.cs……}
None.gif

None.giffile:
//执行事务处理
None.gif
public void DoTran()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif file:
//建立连接并打开
InBlock.gif
 SqlConnection myConn=GetConn();
InBlock.gif myConn.Open();
InBlock.gif SqlCommand myComm
=new SqlCommand();
InBlock.gif
InBlock.gif file:
//SqlTransaction myTran=new SqlTransaction();
InBlock.gif
 file://注意,SqlTransaction类无公开的构造函数
InBlock.gif

InBlock.gif SqlTransaction myTran;
InBlock.gif
InBlock.gif file:
//创建一个事务
InBlock.gif
 myTran=myConn.BeginTransaction();
InBlock.gif 
try
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  file:
//从此开始,基于该连接的数据操作都被认为是事务的一部分
InBlock.gif
  file://下面绑定连接和事务对象
InBlock.gif
  myComm.Connection=myConn;
InBlock.gif  myComm.Transaction
=myTran; file://定位到pubs数据库
InBlock.gif
  myComm.CommandText="USE pubs";
InBlock.gif  myComm.ExecuteNonQuery();
//更新数据
InBlock.gif
  file://将所有的计算机类图书
InBlock.gif
  myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
InBlock.gif  myComm.ExecuteNonQuery();
InBlock.gif 
InBlock.gif  file:
//提交事务
InBlock.gif
  myTran.Commit();
ExpandedSubBlockEnd.gif }

InBlock.gif 
catch(Exception err)
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
ExpandedSubBlockEnd.gif  }

InBlock.gif 
finally
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  myConn.Close();
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif……
None.gif

到此为止,我们仅仅掌握了如何开始和提交事务。下一步我们必须考虑的是在事务中可以干什么和不可以干什么。

另一个走向极端的错误

满怀信心的新手们可能为自己所掌握的部分知识陶醉不已,刚接触数据库库事务处理的准开发者们也一样,踌躇满志地准备将事务机制应用到他的数据处理程序的每一个模块每一条语句中去。的确,事务机制看起来是如此的诱人——简洁、美妙而又实用,我当然想用它来避免一切可能出现的错误——我甚至想用事务把我的数据操作从头到尾包裹起来。

看着吧,下面我要从创建一个数据库开始:

None.gifusing System;
None.gif
using System.Data;
None.gif
using System.Data.SqlClient;
None.gif
None.gif
namespace Aspcn
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif 
public class DbTran
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  file:
//执行事务处理
InBlock.gif
  public void DoTran()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif
InBlock.gif   file:
//建立连接并打开
InBlock.gif
   SqlConnection myConn=GetConn();
InBlock.gif   myConn.Open();
InBlock.gif
InBlock.gif   SqlCommand myComm
=new SqlCommand();
InBlock.gif   SqlTransaction myTran;
InBlock.gif
InBlock.gif   myTran
=myConn.BeginTransaction();
InBlock.gif
InBlock.gif   file:
//下面绑定连接和事务对象
InBlock.gif
   myComm.Connection=myConn;
InBlock.gif   myComm.Transaction
=myTran;
InBlock.gif
InBlock.gif   file:
//试图创建数据库TestDB
InBlock.gif
   myComm.CommandText="CREATE database TestDB";
InBlock.gif   myComm.ExecuteNonQuery();
InBlock.gif
InBlock.gif   file:
//提交事务
InBlock.gif
   myTran.Commit();
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  file:
//获取数据连接
InBlock.gif
  private SqlConnection GetConn()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   
string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
InBlock.gif   SqlConnection myConn
=new SqlConnection(strSql);
InBlock.gif   
return myConn;
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif 
public class Test
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   DbTran tranTest
=new DbTran();
InBlock.gif   tranTest.DoTran();
InBlock.gif   Console.WriteLine(
"事务处理已经成功完成。");
InBlock.gif   Console.ReadLine();
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}
 
None.gif
None.gif
//---------------
None.gif

  未处理的异常: System.Data.SqlClient.SqlException: 在多语句事务内不允许使用 CREATE DATABASE 语句。

None.gifat System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
None.gifat Aspcn.DbTran.DoTran()
None.gifat Aspcn.Test.Main()
None.gif

注意,如下的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语句。

事务回滚

事务的四个特性之一是原子性,其含义是指对于特定操作序列组成的事务,要么全部完成,要么就一件也不做。如果在事务处理的过程中,发生未知的不可预料的错误,如何保证事务的原子性呢?当事务中止时,必须执行回滚操作,以便消除已经执行的操作对数据库的影响。

一般的情况下,在异常处理中使用回滚动作是比较好的想法。前面,我们已经得到了一个更新数据库的程序,并且验证了它的正确性,稍微修改一下,可以得到:

None.gif//RollBack.cs
None.gif
using System;
None.gif
using System.Data;
None.gif
using System.Data.SqlClient;
None.gif
None.gif
namespace Aspcn
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif 
public class DbTran
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  file:
//执行事务处理
InBlock.gif
  public void DoTran()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif
InBlock.gif   file:
//建立连接并打开
InBlock.gif
   SqlConnection myConn=GetConn();
InBlock.gif   myConn.Open();
InBlock.gif
InBlock.gif   SqlCommand myComm
=new SqlCommand();
InBlock.gif   SqlTransaction myTran;
InBlock.gif
InBlock.gif   file:
//创建一个事务
InBlock.gif
   myTran=myConn.BeginTransaction();
InBlock.gif   file:
//从此开始,基于该连接的数据操作都被认为是事务的一部分
InBlock.gif
   file://下面绑定连接和事务对象
InBlock.gif
   myComm.Connection=myConn;
InBlock.gif   myComm.Transaction
=myTran;
InBlock.gif
InBlock.gif   
try
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    file:
//定位到pubs数据库
InBlock.gif
    myComm.CommandText="USE pubs";
InBlock.gif    myComm.ExecuteNonQuery();
InBlock.gif   
InBlock.gif    myComm.CommandText
="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
InBlock.gif    myComm.ExecuteNonQuery();
InBlock.gif
InBlock.gif    file:
//下面使用创建数据库的语句制造一个错误
InBlock.gif
    myComm.CommandText="Create database testdb";
InBlock.gif    myComm.ExecuteNonQuery();
InBlock.gif
InBlock.gif    myComm.CommandText
="UPDATE roysched SET royalty = royalty * 1.20 WHERE title_id LIKE 'Ps%'";
InBlock.gif    myComm.ExecuteNonQuery();
InBlock.gif
InBlock.gif    file:
//提交事务
InBlock.gif
    myTran.Commit();
ExpandedSubBlockEnd.gif   }

InBlock.gif   
catch(Exception err)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    myTran.Rollback();
InBlock.gif    Console.Write(
"事务操作出错,已回滚。系统信息:"+err.Message);
ExpandedSubBlockEnd.gif   }

ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  file:
//获取数据连接
InBlock.gif
  private SqlConnection GetConn()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   
string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
InBlock.gif   SqlConnection myConn
=new SqlConnection(strSql);
InBlock.gif   
return myConn;
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

InBlock.gif 
public class Test
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   DbTran tranTest
=new DbTran();
InBlock.gif   tranTest.DoTran();
InBlock.gif   Console.WriteLine(
"事务处理已经成功完成。");
InBlock.gif   Console.ReadLine();
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

None.gif

首先,我们在中间人为地制造了一个错误——使用前面讲过的Create database语句。然后,在异常处理的catch块中有如下语句:

None.gifmyTran.Rollback();
None.gif

当异常发生时,程序执行流跳转到catch块中,首先执行的就是这条语句,它将当前事务回滚。在这段程序可以看出,在Create database之前,已经有了一个更新数据库的操作——将pubs数据库的roysched表中的所有title_id字段以“PC”开头的书籍的royalty字段的值都增加0.1倍。但是,由于异常发生而导致的回滚使得对于数据库来说什么都没有发生。由此可见,Rollback()方法维护了数据库的一致性及事务的原子性。

使用存储点

事务只是一种最坏情况下的保障措施,事实上,平时系统的运行可靠性都是相当高的,错误很少发生,因此,在每次事务执行之前都检查其有效性显得代价太高——绝大多数的情况下这种耗时的检查是不必要的。我们不得不想另外一种办法来提高效率。

事务存储点提供了一种机制,用于回滚部分事务。因此,我们可以不必在更新之前检查更新的有效性,而是预设一个存储点,在更新之后,如果没有出现错误,就继续执行,否则回滚到更新之前的存储点。存储点的作用就在于此。要注意的是,更新和回滚代价很大,只有在遇到错误的可能性很小,而且预先检查更新的有效性的代价相对很高的情况下,使用存储点才会非常有效。

使用.net框架编程时,你可以非常简单地定义事务存储点和回滚到特定的存储点。下面的语句定义了一个存储点“NoUpdate”:

None.gifmyTran.Save("NoUpdate");

当你在程序中创建同名的存储点时,新创建的存储点将替代原有的存储点。

在回滚事务时,只需使用Rollback()方法的一个重载函数即可:  

None.gifmyTran.Rollback("NoUpdate");
None.gif


下面这段程序说明了回滚到存储点的方法和时机:

None.gifusing System;
None.gif
using System.Data;
None.gif
using System.Data.SqlClient;
None.gif
None.gif
namespace Aspcn
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif 
public class DbTran
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{ 
InBlock.gif  file:
//执行事务处理
InBlock.gif
  public void DoTran()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif
InBlock.gif   file:
//建立连接并打开
InBlock.gif
   SqlConnection myConn=GetConn();
InBlock.gif   myConn.Open();
InBlock.gif
InBlock.gif   SqlCommand myComm
=new SqlCommand();
InBlock.gif   SqlTransaction myTran;
InBlock.gif
InBlock.gif   file:
//创建一个事务
InBlock.gif
   myTran=myConn.BeginTransaction();
InBlock.gif   file:
//从此开始,基于该连接的数据操作都被认为是事务的一部分
InBlock.gif
   file://下面绑定连接和事务对象
InBlock.gif
   myComm.Connection=myConn;
InBlock.gif   myComm.Transaction
=myTran;
InBlock.gif
InBlock.gif   
try
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    myComm.CommandText
="use pubs";
InBlock.gif    myComm.ExecuteNonQuery();
InBlock.gif
InBlock.gif    myTran.Save(
"NoUpdate");
InBlock.gif
InBlock.gif    myComm.CommandText
="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
InBlock.gif    myComm.ExecuteNonQuery();
InBlock.gif
InBlock.gif    file:
//提交事务
InBlock.gif
    myTran.Commit();
ExpandedSubBlockEnd.gif   }

InBlock.gif   
catch(Exception err)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    file:
//更新错误,回滚到指定存储点
InBlock.gif
    myTran.Rollback("NoUpdate");
InBlock.gif    
throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
ExpandedSubBlockEnd.gif   }

ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  file:
//获取数据连接
InBlock.gif
  private SqlConnection GetConn()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   
string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
InBlock.gif   SqlConnection myConn
=new SqlConnection(strSql);
InBlock.gif   
return myConn;
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

InBlock.gif 
public class Test
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
public static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   DbTran tranTest
=new DbTran();
InBlock.gif   tranTest.DoTran();
InBlock.gif   Console.WriteLine(
"事务处理已经成功完成。");
InBlock.gif   Console.ReadLine();
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

None.gif

   很明显,在这个程序中,更新无效的几率是非常小的,而且在更新前验证其有效性的代价相当高,因此我们无须在更新之前验证其有效性,而是结合事务的存储点机制,提供了数据完整性的保证。

隔离级别的概念

企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制的问题。由数据库理论可知,由于并发访问,在不可预料的时刻可能引发如下几个可以预料的问题:

  脏读:包含未提交数据的读取。例如,事务1 更改了某行。事务2 在事务1 提交更改之前读取已更改的行。如果事务1 回滚更改,则事务2 便读取了逻辑上从未存在过的行。

  不可重复读取:当某个事务不止一次读取同一行,并且一个单独的事务在两次(或多次)读取之间修改该行时,因为在同一个事务内的多次读取之间修改了该行,所以每次读取都生成不同值,从而引发不一致问题。

  幻象:通过一个任务,在以前由另一个尚未提交其事务的任务读取的行的范围中插入新行或删除现有行。带有未提交事务的任务由于该范围中行数的更改而无法重复其原始读取。

如你所想,这些情况发生的根本原因都是因为在并发访问的时候,没有一个机制避免交叉存取所造成的。而隔离级别的设置,正是为了避免这些情况的发生。事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别可以增加并发,但代价是降低数据的正确性。相反,较高的隔离级别可以确保数据的正确性,但可能对并发产生负面影响。

根据隔离级别的不同,DBMS为并行访问提供不同的互斥保证。在SQL Server数据库中,提供四种隔离级别:未提交读、提交读、可重复读、可串行读。这四种隔离级别可以不同程度地保证并发的数据完整性:

隔离级别

脏 读

不可重复读取

幻 像

未提交读

提交读

可重复读

可串行读

可以看出,“可串行读”提供了最高级别的隔离,这时并发事务的执行结果将与串行执行的完全一致。如前所述,最高级别的隔离也就意味着最低程度的并发,因此,在此隔离级别下,数据库的服务效率事实上是比较低的。尽管可串行性对于事务确保数据库中的数据在所有时间内的正确性相当重要,然而许多事务并不总是要求完全的隔离。例如,多个作者工作于同一本书的不同章节。新章节可以在任意时候提交到项目中。但是,对于已经编辑过的章节,没有编辑人员的批准,作者不能对此章节进行任何更改。这样,尽管有未编辑的新章节,但编辑人员仍可以确保在任意时间该书籍项目的正确性。编辑人员可以查看以前编辑的章节以及最近提交的章节。这样,其它的几种隔离级别也有其存在的意义。

在.net框架中,事务的隔离级别是由枚举System.Data.IsolationLevel所定义的:

None.gif[Flags]
None.gif[Serializable]
None.gif
public enum IsolationLevel 
None.gif

其成员及相应的含义如下:

成 员

含 义

Chaos

无法改写隔离级别更高的事务中的挂起的更改。

ReadCommitted

在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。

ReadUncommitted

可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。

RepeatableRead

在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。防止不可重复的读取,但是仍可以有幻像行。

Serializable

在DataSet上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。

Unspecified

正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。

显而意见,数据库的四个隔离级别在这里都有映射。

默认的情况下,SQL Server使用ReadCommitted(提交读)隔离级别。

关于隔离级别的最后一点就是如果你在事务执行的过程中改变了隔离级别,那么后面的命名都在最新的隔离级别下执行——隔离级别的改变是立即生效的。有了这一点,你可以在你的事务中更灵活地使用隔离级别从而达到更高的效率和并发安全性。

  最后的忠告

无疑,引入事务处理是应对可能出现的数据错误的好方法,但是也应该看到事务处理需要付出的巨大代价——用于存储点、回滚和并发控制所需要的CPU时间和存储空间。

本文的内容只是针对Microsoft SQL Server数据库的,对应于.net框架中的System.Data.SqlClient命名空间,对于使用OleDb的情形,具体的实现稍有不同,但这不是本文的内容,有兴趣的读者可以到.net中华网(www.aspcn.com)的论坛里找到答案.

转载于:https://www.cnblogs.com/wlq2000/archive/2006/12/15/592960.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436581.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html背景定位,css background-position center left right top bottom代表意思

background-position:center right这里center right分别代表什么,引入对图片背景定位介绍background-position是用于定位背景图片中对象中显示定位,比如图片作为背景时在对象内什么位置开始显示,显示样式等。比如让一个图片作为对象背景距离对象左边或右…

【转】ABP源码分析三十二:ABP.SignalR

Realtime Realtime是ABP底层模块提供的功能,用于管理在线用户。它是使用SignalR实现给在线用户发送通知的功能的前提 IOnlineClient/OnlineClient: 封装在线用户的信息 OnlineClientManager/IOnlineClientManager: 用于提供基本维护在线用户的方法。其内部维护了…

【转】ABP源码分析三十三:ABP.Web

ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现。 AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事 一,在Application_Start完成AbpBootstrapper的初始化。整个ABP系统的初始化就是通过AbpBoo…

【转】ABP源码分析三十四:ABP.Web.Mvc

ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用。 第二,一些常见的基础功能的实现。 AbpController:这是一个抽象基类…

地球化学图解系统GCDPlot 0.33

地球化学 图解 系统GCDPlot 0.33发布,新增了Excel 2007支持。下载 地址:GCDPlot下载GCDPlot 是一个进行地球化学图解、CIPW计算的 Microsoft Excel VBA 程序,GCDPlot 可以使用 Excel 的数据以标准的 Excel 图表格式绘制各类地球化学图解&am…

【转】ABP源码分析三十五:ABP中动态WebAPI原理解析

动态WebAPI应该算是ABP中最Magic的功能之一了吧。开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能,这应该算是对DRY的最佳诠释了. 如下图所示,一行代码就为所有实现了IApplicationSer…

jstl处理栏目与子栏目_Detelogy智能前处理设备微展厅P2:再添新品

时隔小半年,新一轮Detelogy产品总览展示栏目再与大家见面。事不宜迟,点击视频即刻进入我们的智能前处理设备微展厅。 知乎视频​www.zhihu.com01 高效多样品前处理系统02 智能浓缩设备全系列03 智能湿法消解设备应用领域农残、兽残检测有机磷类、有机氯类…

【转】ABP源码分析三十六:ABP.Web.Api

这里的内容和ABP 动态webapi没有关系。除了动态webapi,ABP必然是支持使用传统的webApi。ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net webApi。 AbpApiController:这是一个抽象基类,继承自ApiControl…

408最后计算机网络题库,2021考研计算机统考408专业基础综合题库

**部分为历年考研真题。提供2009~2018年考研真题及参考答案,其中2009~2015年每道真题均提供详细答案解析,通过对真题的演练和分析,可以帮助学员牢牢抓住计算机学科专业基础综合考试的命题特点,提高复习效率…

Unity-Shader-渲染队列,ZTest,ZWrite

Unity-Shader-渲染队列,ZTest,ZWrite ZTest(深度测试)和ZWrite(深度写入)ZTest Less(深度小于当前缓存则通过)ZTest Greater(深度大于当前缓存则通过)ZTest L…

如何做好职业规划(乾卦)

职业一般来说会占用我们每个人生命中的大部分时间,而职业发展中变化莫测的因数常常令我们束手无策、捉襟见肘。在面对变化多端的情况时,中国人的传统智慧就发挥出最大的优势了。如何将快速变化 、不好掌控的事情处理得井井有条,这需要我们懂得…

850是什么意思_沃尔沃为什么不是一线豪华品牌?

1927年诞生的沃尔沃,它造车的历史比宝马还要略早几年。但经过近百年的发展,宝马品牌无论在国际上还是我们国内,以销量还是品牌含金量来看,都是众所公认的豪华品牌第一阵营成员(奔驰、宝马,奥迪)…

【转】ABP源码分析三十七:ABP.Web.Api Script Proxy API

ABP提供Script Proxy WebApi为所有的Dynamic WebApi生成访问这些WebApi的JQuery代理,AngularJs代理以及TypeScriptor代理。这些个代理就是javascript脚本,通过这些代理可以简单的访问Dynamic webApi。 如下实例演示一个最基本的应用场景。首先通过Script Proxy Web…

word2003如何设置护眼模式_手机屏幕的护眼模式是如何保护你的眼睛?

公司业务包括:二手机回收,二手机销售,配件批发,以及手机维修等业务,目前公司业务覆盖山西全境以及周边省市,在同行业有较高知名度。 随着智能手机的普及,大多数人也逐渐开始沉迷于每天的刷手…

WinCE系统的编译过程

作者:ARM-WinCE 在WinCE系统中,当我们完成了相关的开发和系统定制工作以后,会编译WinCE系统,最后生成NK.bin和NK.nb0。我现在用WinCE6.0在自己的PC上面编译一次用时19分16秒(有一天无聊,就测了一下)。下面介绍一下WinC…

【转】ABP源码分析三十八: ABP.Web.Api.OData

如果对OData不熟悉的话可参考OData的初步认识一文以获取OData的一些初步知识。 API.Odata 模块唯一用处就是提供了一个泛型版本的ODataController&#xff0c;实现了Controller代码的常用。 AbpODataEntityController<TEntity, TPrimaryKey>&#xff1a;使用ABP的repos…

饭卡可以用水冲洗吗_薄壁不锈钢水管真的可以满足大众用水健康管道的要求吗?...

我们都认识现如今的自来水的出厂干净程度达到安全、卫生&#xff0c;干净标准。但是&#xff0c;很是收到不少市民的投诉&#xff0c;说日常饮用的自来水有异味、浑浊、杂质&#xff0c;为何会出现这番现象呢&#xff0c;为此&#xff0c;我国也针对此研究调查&#xff0c;最后…

WinCE的快速启动

PXA270上的双启动功能已经实现了&#xff0c;但又引入了一个新问题&#xff0c;WinCE的启动太慢&#xff0c;大概需要2分钟&#xff01;这显然是不能忍受的。通过两天的努力&#xff0c;现在已经缩短到20秒&#xff0c;虽然还是有点长&#xff0c;但已改善不少了。先整理一下&a…

【转】ABP源码分析三十九:ABP.Hangfire

ABP对HangFire的集成主要是通过实现IBackgroundJobManager接口的HangfireBackgroundJobManager类完成的。 HangfireBackgroundJobManager:实现了接口IBackgroundJobManager中的方法EnqueueAsync&#xff0c;通过HangfireBackgroundJob完成Enqueue。重写了BackgroundWorkerBase…

计算机无法显示硬盘盘符,电脑硬盘不显示盘符怎么办 移动硬盘不显示盘符的原因...

电脑上是目前我们经常会使用到电子设备&#xff0c;其中电脑硬盘是电脑重要的零部件&#xff0c;它可以为电脑存储和运行各种文件&#xff0c;是电脑不可或缺的组件&#xff0c;然后用户在使用电脑的时候突然发现电脑硬盘不显示盘符该怎么办&#xff0c;移动硬盘显不示盘符的有…