C#数据库事务原理及实践
什么是数据库事务
数据库事务是指作为单个逻辑工作单元执行的一系列操作。
��设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:
· 更新客户所购商品的库存信息
· 保存客户付款信息--可能包括与银行系统的交互
· 生成订单并且保存到数据库中
· 更新用户相关信息,例如购物数量等等
��正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信
息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商
品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失
败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失
败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有
被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可
预测。
��数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。
数据库事务的 ACID 属性
��事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面
向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可
以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓
的 ACID(原子性、一致性、隔离性和持久性)属性:
· 原子性
��事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通
常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些
操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可
能性。
· 一致性
��事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都
必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构
(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发
人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转
帐的应用程序时,应避免在转帐过程中任意移动小数点。
· 隔离性
��由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时
数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后
的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数
据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务
可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果
与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,
所以一些应用程序降低隔离级别以换取更大的吞吐量。
· 持久性
�� 事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也
将一直保持。
DBMS 的责任和我们的任务
��企业级的数据库管理系统(DBMS)都有责任提供一种保证事务的物理完整性的机
制。就常用的 SQL Server2000 系统而言,它具备锁定设备隔离事务、记录设备保证事
务持久性等机制。因此,我们不必关心数据库事务的物理完整性,而应该关注在什么情
况下使用数据库事务、事务对性能的影响,如何使用事务等等。
��本文将涉及到在.net 框架下使用 C#语言操纵数据库事务的各个方面。
体验 SQL 语言的事务机制
��作为大型的企业级数据库,SQL Server2000 对事务提供了很好的支持。我们可以使
用 SQL 语句来定义、提交以及回滚一个事务。
��如下所示的 SQL 代码定义了一个事务,并且命名为"MyTransaction"(限于篇幅,
本文并不讨论如何编写 SQL 语言程序,请读者自行参考相关书籍):
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 倍。