事务是一组组合成逻辑工作单元的数据库操作,在系统执行过程中可能会出错,但事务将控制和维护每个数据库的一致性和完整性。事务处理的主要特征是,任务要么全部完成,要么都不完成。在写入一些记录时,要么写入所有记录,要么什么都不写入。如果在写入一个记录时出现了一个失败,那么在事务处理中已写入的其他数据就会回滚。事务可能由很多单个任务构成。
简单事务的一个常见例子:把钱从A账户转到B账户,这涉及两项任务,即从A账户把钱取出来;把钱存入B账户。两项任务要么同时成功,要么一起失败,给予回滚,以便保持账户的状态和原来相同。否则,在执行某一个操作的时候可能会因为停电、网络中断等原因而出现故障,所以有可能更新了一个表中的行,但没有更新相关表中的行。如果数据库支持事务,则可以将数据库操作组成一个事务,以防止因这些事件而使数据库出现不一致。
事务的ACID属性如下。
l  原子性(Atomicity):事务的所有操作是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。原子性消除了系统处理操作子集的可能性。
l  一致性(Consistency):数据从一种正确状态转换到另一种正确状态。事务在完成时,必须使所有的数据都保持一致。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。当事务结束时,所有的内部数据结构都必须是正确的。在存款取款的例子中,逻辑规则是,钱是不能凭空产生或销毁的,对于每个(收支)条目必须有一个相应的抵衡条目产生,以保证账户是平的。
l  隔离性(Isolation):由并发事务所作的修改必须与任何其他并发事务所作的修改隔离。查看数据时数据所处的状态,要么是事务修改它之前的状态,要么是事务修改它之后的状态。简单的理解就是,防止多个并发更新彼此干扰。事务在操作数据时与其他事务操作隔离。隔离性一般是通过加锁的机制来实现的。
l  持久性(Durability):事务完成之后,它对于系统的影响是永久性的。已提交的更改即使在发生故障时也依然存在。
对于事务的开发,.NET平台也为我们提供了几种非常简单方便的事务机制。无论是在功能上还是性能上都提供了优秀的企业级事务支持。
.NET开发者可以使用以下5种事务机制:
 
1、  SQL和存储过程级别的事务。(目前我在用)
2、 ADO.NET级别的事务。(目前我在用)
3、  ASP.NET页面级别的事务。(目前我在用)
4、 企业级服务COM+事务。(没用过啊)
5、  System.Transactions 事务处理。(目前我在用)
 
这5种事务机制有着各自的优势和劣势,分别表现在性能、代码数量和部署设置等方面。开发人员可以根据项目的实际情况选择相应的事务机制。下面就开始分别说明日常开发中5种事务的具体使用。
 
------------------------------------------------
目前正在用的方法,推荐!!
 
.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 方法,将导致退出该块时请求提交该事务。此方法还提供了内置的错误处理,出现异常时会终止事务。
                                     (示例位置:光盘\code\ch05\04\ClassTran\OrderData3
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 对事务提供了很好的支持,但是没有必要总是使用事务。使用事务的第一条规则是,在能够使用事务的时候都应该使用事务,但是不要使用过度。原因在于,每次使用事务都会占用一定的开销。另外,事务可能会锁定一些表的行。还有一条规则是,只有当操作需要的时候才使用事务。例如,如果只是从数据库中查询一些记录,或者执行单个查询,则在大部分时候都不需要使用显式事务。
开发人员应该在头脑中始终保持一个概念,就是用于修改多个不同表数据的冗长事务会严重妨碍系统中的所有其他用户。这很可能导致一些性能问题。当实现一个事务时,遵循下面的实践经验能够达到可接受的结果:
避免使用在事务中的 Select 返回数据,除非语句依赖于返回数据。
如果使用 Select 语句,则只选择需要的行,这样不会锁定过多的资源,而尽可能地提高性能。
尽量将事务全部写在 T-SQL 或者 API 中。
避免事务与多重独立的批处理工作结合,应该将这些批处理放置在单独的事务中。
尽可能避免大量更新。
另外,必须注意的一点就是事务的默认行为。在默认情况下,如果没有显式地提交事务,则事务会回滚。虽然默认行为允许事务的回滚,但是显式回滚方法总是一个良好的编程习惯。这不仅仅只是释放锁定数据,也将使得代码更容易读取并且更少错误。
      .NET 提供的事务功能很强大,具体的内容远不止本文所讲解的这样简单。本文只是起到一个抛砖引玉的功能。希望读者能够灵活恰当地使用事务功能,而不要过度使用事务,否则可能会对性能起到消极的作用。
 
--------------------------------------