C#中分布式事务的超时处理问题

事务是个很精妙的存在,我们在数据层、服务层、业务逻辑层等多处地方都会使用到。

在这里我只说下TransactionScope这个微软推荐使用的隐式事务。它是从Framework 2.0开始引入的一个事务管理类,在使用隐式事务时,事务完成前 程序应调用TransactionScope的Complete()方法,将事务提交,然后利用Dispose()释放事务对象。若执行期间出现错误,事务将自动回滚。

比如:
    using (ransactionScope scope = new TransactionScope())

{

//to do something

scope.Complete();

}

在这里个人建议用using来创建,因为using实现了IDispose接口,它会隐式的调用TransactionScope对象的Dispose方法,即使发生异常时也是如此,能确保在事务结束或者异常的时候也能正确的释放资源。其实我们反编译一下,它的内部实现就是一个try...finally代码块,这样也就不难理解using的作用了。

说主题,在某地市的某库升级中,为避免程序运行中产生脏数据以及数据更新不一致导致的重复同步情况,在可能产生上述问题的考虑下,我用这个TransactionScope来对上述的操作进行事务处理。在本机的测试环境中,运行结果是正常的,当然这个运行正常的前提是数据量较小的情况下,我每次只对一条或者十几条数据的不同表进行insert和update。然而部署到生产环境针对真实数据运行之后,发现这个事务总是回滚,一直无法正常提交。程序也就没法正常跑起来。因为生产环境中的数据有60W左右,insert一次、update一次,最后再insert一条同步语句,前2个操作都是比较耗时的。我切换回测试环境调试了一下,逐行运行,发现当执行完第一个insert之后,执行第二个update时发生异常了。这个异常由TransactionScope抛出,异常提示是:事务已中止。这个错误,在数据量小的情况下不会发生,数据量大一些就出现了,这个是不是和事务处理的时间长短有关呢?因为我明显感觉到在这次调试的时候,执行的时间比之前数据量只有一条的时候长了很多,至少花费1分钟以上。于是google一下,验证了我的想法。
       TransactionScope有些重载函数是可以接受TimeSpan类型的值,这个就是事务的超时时间了。当事务实现隔离的时候,事务范围内的资源将会被锁定,如果一些事务长期占有资源,那将很容易造成死锁,为了避免这个问题,TransactionScope事务会定义一个超时限制,这个超时默认值为60秒。如果事务超过此时间,即使没有发生异常,也会自动中止。上面问题的原因算是找到了,知道了原因,那么也就很好解决了。我们可以在Web.Config 中配置: 
<configuration>

<system.transactions>

<defaultSettings timeout="00:05:00" />

</system.transactions>

</configuration>

或者在using的时候就定义好超时时间:

 
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,
new TimeSpan(0, 5, 0)))

或者先初始化事物行为的附加信息,然后定义超时时间:

TransactionOptions tOpt = new TransactionOptions();

tOpt.IsolationLevel = IsolationLevel.ReadCommitted; //设置TransactionOptions模式

tOpt.Timeout = new TimeSpan(0, 5, 0); // 设置超时时间为5分钟                        
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, tOpt))

我这里定义的是5分钟,其实整个过程处理起来也就第一次处理历史数据需要1到2分钟时间,以后每天只需处理几十条数据,这个时间基本是秒级别的。

这里说明下, 超时时间如果设置为0时表示超时无限长。无限长的设置主要对调试有用,调试过程中可能要逐步通过代码来隔离业务逻辑中的问题,并且在尝试确定问题期间不希望所调试的事务超时。在所有其他情况下使用无限长的超时时一定要格外小心,因为它会覆盖防止事务死锁的保护。[这段说明摘抄至MSDN]

1、SQL事务

优点:执行效率最佳

限制:事务上下文仅在数据库中调用,难以实现复杂的业务逻辑。

  1. CREATE PROCEDURE Tran1
  2. as
  3. begin tran
  4. set xact_abort on
  5. Insert Into trantest (id,test)values(1,'test')
  6. Insert Into trantest (id,test)values(2,'test')
  7. commit tran
  8. GO
  9. --set xact_abort on  表示遇到错误立即回滚
  10. --当然你也可以这么写
  11. CREATE PROCEDURE tran1
  12. as
  13. begin tran
  14. insert into trantest(id,test)values(1,'test')
  15. if(@@error<>0)
  16. rollback tran
  17. else
  18. begin
  19. insert into trantest(id,test)values(2,'test')
  20. if(@@error<>0)
  21. rollback tran
  22. else
  23. commit tran
  24. end
  25. GO
  1. CREATE PROCEDURE Tran1
  2. as
  3. begin tran
  4. set xact_abort on
  5. Insert Into trantest (id,test)values(1,'test')
  6. Insert Into trantest (id,test)values(2,'test')
  7. commit tran
  8. GO
  9. --set xact_abort on  表示遇到错误立即回滚
  10. --当然你也可以这么写
  11. CREATE PROCEDURE tran1
  12. as
  13. begin tran
  14. insert into trantest(id,test)values(1,'test')
  15. if(@@error<>0)
  16. rollback tran
  17. else
  18. begin
  19. insert into trantest(id,test)values(2,'test')
  20. if(@@error<>0)
  21. rollback tran
  22. else
  23. commit tran
  24. end
  25. GO

2、ADO.NET 事务

在ADO.NET 中,可以使用Connection 和Transaction 对象来控制事务。若要执行事务,请执行下列操作:
调用Connection 对象的BeginTransaction 方法来标记事务的开始。
将Transaction 对象分配给要执行的Command的Transaction 属性。
执行所需的命令。

调用Transaction 对象的Commit 方法来完成事务,或调用Rollback 方法来取消事务。

优点:简单,效率和数据库事务差不多快。

缺点:事务执行在数据库连接层上,所以你需要在事务过程中手动的维护一个连接。

  1. SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString);
  2. conn.Open();
  3. SqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);
  4. SqlCommand cmd = new SqlCommand();
  5. cmd.Connection = conn;
  6. cmd.Transaction = tx;
  7. try
  8. {
  9. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
  10. cmd.ExecuteNonQuery();
  11. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
  12. cmd.ExecuteNonQuery();
  13. tx.Commit();
  14. }
  15. catch (Exception ex)
  16. {
  17. tx.Rollback();
  18. throw new Exception(ex.Message, ex);
  19. }
  20. finally
  21. {
  22. conn.Close();
  23. }
  1. SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString);
  2. conn.Open();
  3. SqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);
  4. SqlCommand cmd = new SqlCommand();
  5. cmd.Connection = conn;
  6. cmd.Transaction = tx;
  7. try
  8. {
  9. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
  10. cmd.ExecuteNonQuery();
  11. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
  12. cmd.ExecuteNonQuery();
  13. tx.Commit();
  14. }
  15. catch (Exception ex)
  16. {
  17. tx.Rollback();
  18. throw new Exception(ex.Message, ex);
  19. }
  20. finally
  21. {
  22. conn.Close();
  23. }

3、TransactionScope事务

在.NET 2.0中新添加了一个名为System.Transactions的命名空间,其提供了一个“轻量级”的、易于使用的事务框架,通过这个框架可以大大简化事务的操作。
这个框架提供了如下优点:
(1)在简单(不涉及分布式)事务中也可以使用声明式的事务处理方法,而不必使用Com+容器和目录注册。
(2)用户根本不需要考虑是简单事务还是分布式事务。它实现一种所谓自动提升事务机制(Promotable Transaction),会自动根据事务中涉及的对象资源判断使用何种事务管理器。
TransactionScope事务类,它可以使代码块成为事务性代码。并自动提升为分布式事务

优点:实现简单,同时能够自动提升为分布式事务

  1. TransactionOptions option = new TransactionOptions();
  2. option.IsolationLevel = IsolationLevel.ReadCommitted;
  3. using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,option))
  4. {
  5. using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
  6. {
  7. conn.Open();
  8. SqlCommand cmd = new SqlCommand(conn);
  9. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
  10. cmd.ExecuteNonQuery();
  11. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
  12. cmd.ExecuteNonQuery();
  13. }
  14. ts.Complete();
  15. }
  1. TransactionOptions option = new TransactionOptions();
  2. option.IsolationLevel = IsolationLevel.ReadCommitted;
  3. using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,option))
  4. {
  5. using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
  6. {
  7. conn.Open();
  8. SqlCommand cmd = new SqlCommand(conn);
  9. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
  10. cmd.ExecuteNonQuery();
  11. cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
  12. cmd.ExecuteNonQuery();
  13. }
  14. ts.Complete();
  15. }

4、EnterpriseServices实现事务

就是利用com+实现自动处理事务。

添加引用System.EnterpriseServices.dll
using System.EnterpriseServices;

使用方法参考:http://support.microsoft.com/default.aspx?scid=kb;zh-cn;816141

随便建立一个按钮,在按钮中进行如下操作:

  1. try
  2. {
  3. work1();
  4. work2();
  5. ContextUtil.SetComplete();
  6. }
  7. catch(System.Exception except)
  8. {
  9. ContextUtil.SetAbort();
  10. Response.Write(except.Message);
  11. }
  1. try
  2. {
  3. work1();
  4. work2();
  5. ContextUtil.SetComplete();
  6. }
  7. catch(System.Exception except)
  8. {
  9. ContextUtil.SetAbort();
  10. Response.Write(except.Message);
  11. }

然后在页面中添加2个操作,模拟一下在逻辑层调用不同类中的操作的情况 :

  1. private void work1()
  2. {
  3. SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["conn"]);
  4. SqlCommand cmd1=new SqlCommand("Insert Into trantest (id,test)values(1,'test')",conn);
  5. conn.Open();
  6. cmd1.ExecuteNonQuery();
  7. conn.Close();
  8. }
  9. private void work2()
  10. {
  11. SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["conn"]);
  12. SqlCommand cmd2=new SqlCommand("Insert Into trantest (id,test)values(2,'test')",conn);
  13. conn.Open();
  14. cmd2.ExecuteNonQuery();
  15. conn.Close();
  16. }
  17. 修改前台页面在<%Page后面添加 Transaction="Required" 即可
 
上一篇:JSP面试题及答案


下一篇:skip index scan