概述
事务必须具有原子性、一致性、隔离性和持久性。虽然首字母缩写词容易记忆,但是每个词的含义不是很明显。以下是简要说明。
● 原子性(Atomicity):原子性可确保要么执行所有更新,要么什么也不发生。由于事务中的原子性保障,开发人员不必编写代码来处理更新成功而另一个没有成功的情况。
●
一致性(Consistency):一致性意味着事务的结果使得系统保持一致状态。在事务启动之前,数据保持有效的状态,这与事务结束时一样。一致性还确
保了事务必须使得数据库保持一致状态,如果事务的部分操作失败,则其他部分也必须回到原来的状态。
● 隔离性(Isolation):多个用户可能同时访问同一个数据库。使用隔离性能够保证在事务完成之前,该事务外部不能看到事务中的数据改变。也不能访问一些中间状态,如果事务终止这些状态将不会发生。
● 持久性(Durability):持久性意味着即使是系统崩溃也能够保证一致性状态。如果数据库系统崩溃,则持久性必须保证已经提交的事务确实写入了数据库。
事务类型
事务分为本地事务和分布式事务两种类型。
● 本地事务:该类型事务使用已知数据源(例如SQL
Server),同时还是单阶段事务。若单个数据库中保存了所有有关事务的数据,对自身可以强制使用ACID规则。这意味着在单个数据库服务器中(例如
SQL Server),只要使用同一个连接,则可以跨数据库使用本地事务。
● 分布式事务:该类型事务使用多个已知事务数据源。分布式行为可能需要从消息队列服务器中读取消息,从SQL Server数据库中获取数据,以及将消息写入其他数据库。
一
些软件包(例如MSDTC)能够以编程方式辅助实现分布式事务,通过使用一些方法(例如两阶段提交和回滚)能够控制跨越所有数据源的提交和回滚行为,以便
保证集成性。MSDTC仅可用于兼容事务管理接口的应用程序。当前可用的应用程序有MSMQ、SQL
Server、Oracle、Sybase和其他当前可用的应用程序(称为资源管理器)。
两阶段提交
在分布式事务环境中,不同的资源管理器需要实现可靠的提交协议,最为常见的实现是两阶段提交。在两阶段提交中,实际的提交工作分为两个阶段:
● 第一个阶段包括为提交准备一些所需的更改。这样,RM(资源管理器)就会与事务协调器通信,告知其更新准备已经就绪,准备执行提交,但实际还不进行提交。
● 一旦所有资源管理器都告知事务协调器准备工作就绪,那么事务协调器将使所有参与者都了解继续工作准备好,接着执行更改。
在
两阶段提交中,单个或者多个数据库能够参与分布式事务。实际上,任何在MSDTC事务中登记的对象都能够参与由MSDTC管理的分布式事务。例
如,MSMQ能够参与由两个SqlConnection对象连接两个不同数据库的事务。简单描述两阶段提交显然过于简单化,而深入讲解两阶段提交又超出了
本书范围。既然读者对事务有了初步认识,就能够理解.NET 1.x提供的针对事务过程的支持。
四种事务处理
一、SQL事务
sql事务是使用SQL server自身的事务:在存储过程中直接使用Begin Tran,Rollback Tran,Commit Tran实现事务:
优点:执行效率最佳
限制:事务上下文仅在数据库中调用,难以实现复杂的业务逻辑。
示例:( SQL Server自带的AdventureWorks数据为例)
带事务的储存过程:
01 |
CREATE PROCEDURE dbo.spModifyAddress |
05 |
@PostalCode nvarchar(15), |
10 |
Update Address Set City=@City,PostalCode=@PostalCode where AddressID=@AddressID |
11 |
update AddressType set Name = @Name where AddressTypeID = @AddressID |
12 |
declare @UpdateError int |
13 |
select @UpdateError=@@error |
调用:
03 |
SqlConnection conn = new SqlConnection( "Data Source=127.0.0.1;Initial Catalog=AdventureWorks;Persist Security Info=True;User ID=sa;Password=123;" ); |
04 |
SqlCommand cmd = new SqlCommand(); |
05 |
cmd.CommandText = "spModifyAddress" ; |
06 |
cmd.CommandType = CommandType.StoredProcedure; |
07 |
cmd.Connection = conn; |
09 |
SqlParameter[] paras = new SqlParameter[]{ |
10 |
new SqlParameter ( "@AddressID" ,SqlDbType.Int,4), |
11 |
new SqlParameter ( "@City" ,SqlDbType.NVarChar,30), |
12 |
new SqlParameter ( "@PostalCode" ,SqlDbType.NVarChar,32), |
13 |
new SqlParameter ( "@Name" ,SqlDbType.NVarChar ,50)}; |
15 |
paras[1].Value = "zhejang" ; |
16 |
paras[2].Value = "315000" ; |
17 |
paras[3].Value = "group" ; |
18 |
foreach (SqlParameter para in paras) |
20 |
cmd.Parameters.Add(para); |
22 |
cmd.ExecuteNonQuery(); |
二、ADO.net事务
Ado.net事务可能是大家一般都用的
优点:简单,效率和数据库事务差不多。
缺点:事务不能跨数据库,只能在一个数据库连接上。如果是两个数据库上就不能使用该事务了。
示例:
03 |
SqlConnection conn = new SqlConnection( "Data Source=127.0.0.1;Initial Catalog=AdventureWorks;Persist Security Info=True;User ID=sa;Password=123;" ); |
04 |
SqlTransaction sqlTran = conn.BeginTransaction(); |
05 |
SqlCommand cmd = new SqlCommand(); |
06 |
cmd.CommandText = "spModifyAddress" ; |
07 |
cmd.CommandType = CommandType.StoredProcedure; |
08 |
cmd.Connection = conn; |
10 |
cmd.Transaction = sqlTran; |
14 |
cmd.ExecuteNonQuery(); |
29 |
private void AddParameter(SqlCommand cmd) |
31 |
SqlParameter[] paras = new SqlParameter[]{ |
32 |
new SqlParameter ( "@AddressID" ,SqlDbType.Int,4), |
33 |
new SqlParameter ( "@City" ,SqlDbType.NVarChar,30), |
34 |
new SqlParameter ( "@PostalCode" ,SqlDbType.NVarChar,32), |
35 |
new SqlParameter ( "@Name" ,SqlDbType.NVarChar ,50)}; |
37 |
paras[1].Value = "zhejang" ; |
38 |
paras[2].Value = "315000" ; |
39 |
paras[3].Value = "group" ; |
40 |
foreach (SqlParameter para in paras) |
42 |
cmd.Parameters.Add(para); |
三、TransactionScope事务
TransactionScope事务类,它可以使代码块成为事务性代码。并自动提升为分布式事务
优点:实现简单,同时能够自动提升为分布式事务。
使用时要保证MSDTC服务(控制分布事务)是开启的可以使用
可以使用开启服务命令:net start msdtc
还有使防火墙MS DTC 服务使用网络或打开 MS DTC 端口 命令:netsh firewall set allowedprogram %windir%\system32\msdtc.exe MSDTC enable
04 |
SqlConnection conn = new SqlConnection( "Data Source=127.0.0.1;Initial Catalog=AdventureWorks;Persist Security Info=True;User ID=sa;Password=123;" ); |
05 |
using (TransactionScope ts = new TransactionScope()) |
08 |
SqlCommand cmd = new SqlCommand(); |
09 |
cmd.CommandText = "spModifyAddress" ; |
10 |
cmd.CommandType = CommandType.StoredProcedure; |
11 |
cmd.Connection = conn; |
16 |
cmd.ExecuteNonQuery(); |
32 |
private void AddParameter(SqlCommand cmd) |
34 |
SqlParameter[] paras = new SqlParameter[]{ |
35 |
new SqlParameter ( "@AddressID" ,SqlDbType.Int,4), |
36 |
new SqlParameter ( "@City" ,SqlDbType.NVarChar,30), |
37 |
new SqlParameter ( "@PostalCode" ,SqlDbType.NVarChar,32), |
38 |
new SqlParameter ( "@Name" ,SqlDbType.NVarChar ,50)}; |
40 |
paras[1].Value = "zhejang" ; |
41 |
paras[2].Value = "315000" ; |
42 |
paras[3].Value = "group" ; |
43 |
foreach (SqlParameter para in paras) |
45 |
cmd.Parameters.Add(para); |
四、COM+事务
在分布式应用程序中,往往要同时操作多个数据库,使用数据库事务就不能满足业务的要求了。在COM+中,提供完整的事务处理服务。很方便处理多个数据库上的事务。
示例:
04 |
SqlConnection conn = new SqlConnection( "Data Source=127.0.0.1;Initial Catalog=AdventureWorks;Persist Security Info=True;User ID=sa;Password=123;" ); |
05 |
ServiceConfig sc = new ServiceConfig(); |
06 |
sc.Transaction = TransactionOption.Required; |
08 |
sc.TrackingEnabled = true ; |
09 |
ServiceDomain.Enter(sc); |
12 |
SqlCommand cmd = new SqlCommand(); |
13 |
cmd.CommandText = "spModifyAddress" ; |
14 |
cmd.CommandType = CommandType.StoredProcedure; |
15 |
cmd.Connection = conn; |
20 |
cmd.ExecuteNonQuery(); |
21 |
ContextUtil.SetComplete(); |
26 |
ContextUtil.SetAbort(); |
33 |
ServiceDomain.Leave(); |
40 |
private void AddParameter(SqlCommand cmd) |
42 |
SqlParameter[] paras = new SqlParameter[]{ |
43 |
new SqlParameter ( "@AddressID" ,SqlDbType.Int,4), |
44 |
new SqlParameter ( "@City" ,SqlDbType.NVarChar,30), |
45 |
new SqlParameter ( "@PostalCode" ,SqlDbType.NVarChar,32), |
46 |
new SqlParameter ( "@Name" ,SqlDbType.NVarChar ,50)}; |
48 |
paras[1].Value = "zhejang" ; |
49 |
paras[2].Value = "315000" ; |
50 |
paras[3].Value = "group" ; |
51 |
foreach (SqlParameter para in paras) |
53 |
cmd.Parameters.Add(para); |
事务和性能
在头脑中始终保持一个概念,就是用于修改多个不同表数据的冗长事务会严重妨碍系统中的所有其他用户。这很可能导致一些性能问题。当实现一个事务时,遵循下面的实践经验能够达到可接受的结果:
● 尽可能短的保持事务。
● 避免使用在事务中的SELECT返回数据,除非语句依赖于返回数据。
● 如果使用SELECT语句,只选择需要的行,因此不要锁定过多的资源同时保持尽可能高的性能。在架构语序的情况下,从事务中移出所有SELECT语句。
●
尽量将事务全部写在T-SQL或者API中。混合和匹配将导致混乱。同样,尽量在客户端使用API封装事务,而不是T-SQL。举例而言,当需要使用T-
SQL完全封装事务时,如果那正是需要的,就可以完全接受。开发人员应该避免使用SqlTransaction作为事务的开始,然后使用存储过程回滚或者
提交,或者其他类似方式。
● 避免事务与多重独立的批处理工作结合。将这些批处理放置在单独的事务中。
● 尽可能避免大量更新。当然这并不意味着为了避免大量更新而应该放弃事务的优越性。但是确保避免不必要的增加事务的大小非常重要,因为这么做会锁定更多资源。
必须注意的一点就是事务的默认行为。在默认情况下,如果没有显式地提交事务,则事务会回滚。虽然默认行为允许事务的回滚,但是显式回滚方法总是一个良好的编程习惯。这不仅仅只是释放锁定数据,也使得代码更容易读并且更少错误。
步步为营 C# 技术漫谈系列
作者:spring yang
出处:http://www.cnblogs.com/springyangwc/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。