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的时候就定义好超时时间:
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事务
优点:执行效率最佳
限制:事务上下文仅在数据库中调用,难以实现复杂的业务逻辑。
- CREATE PROCEDURE Tran1
- as
- begin tran
- set xact_abort on
- Insert Into trantest (id,test)values(1,'test')
- Insert Into trantest (id,test)values(2,'test')
- commit tran
- GO
- --set xact_abort on 表示遇到错误立即回滚
- --当然你也可以这么写
- CREATE PROCEDURE tran1
- as
- begin tran
- insert into trantest(id,test)values(1,'test')
- if(@@error<>0)
- rollback tran
- else
- begin
- insert into trantest(id,test)values(2,'test')
- if(@@error<>0)
- rollback tran
- else
- commit tran
- end
- GO
- CREATE PROCEDURE Tran1
- as
- begin tran
- set xact_abort on
- Insert Into trantest (id,test)values(1,'test')
- Insert Into trantest (id,test)values(2,'test')
- commit tran
- GO
- --set xact_abort on 表示遇到错误立即回滚
- --当然你也可以这么写
- CREATE PROCEDURE tran1
- as
- begin tran
- insert into trantest(id,test)values(1,'test')
- if(@@error<>0)
- rollback tran
- else
- begin
- insert into trantest(id,test)values(2,'test')
- if(@@error<>0)
- rollback tran
- else
- commit tran
- end
- GO
2、ADO.NET 事务
在ADO.NET 中,可以使用Connection 和Transaction 对象来控制事务。若要执行事务,请执行下列操作:
调用Connection 对象的BeginTransaction 方法来标记事务的开始。
将Transaction 对象分配给要执行的Command的Transaction 属性。
执行所需的命令。
调用Transaction 对象的Commit 方法来完成事务,或调用Rollback 方法来取消事务。
优点:简单,效率和数据库事务差不多快。
缺点:事务执行在数据库连接层上,所以你需要在事务过程中手动的维护一个连接。
- SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString);
- conn.Open();
- SqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);
- SqlCommand cmd = new SqlCommand();
- cmd.Connection = conn;
- cmd.Transaction = tx;
- try
- {
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
- cmd.ExecuteNonQuery();
- tx.Commit();
- }
- catch (Exception ex)
- {
- tx.Rollback();
- throw new Exception(ex.Message, ex);
- }
- finally
- {
- conn.Close();
- }
- SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString);
- conn.Open();
- SqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);
- SqlCommand cmd = new SqlCommand();
- cmd.Connection = conn;
- cmd.Transaction = tx;
- try
- {
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
- cmd.ExecuteNonQuery();
- tx.Commit();
- }
- catch (Exception ex)
- {
- tx.Rollback();
- throw new Exception(ex.Message, ex);
- }
- finally
- {
- conn.Close();
- }
3、TransactionScope事务
在.NET 2.0中新添加了一个名为System.Transactions的命名空间,其提供了一个“轻量级”的、易于使用的事务框架,通过这个框架可以大大简化事务的操作。
这个框架提供了如下优点:
(1)在简单(不涉及分布式)事务中也可以使用声明式的事务处理方法,而不必使用Com+容器和目录注册。
(2)用户根本不需要考虑是简单事务还是分布式事务。它实现一种所谓自动提升事务机制(Promotable Transaction),会自动根据事务中涉及的对象资源判断使用何种事务管理器。
TransactionScope事务类,它可以使代码块成为事务性代码。并自动提升为分布式事务
优点:实现简单,同时能够自动提升为分布式事务
- TransactionOptions option = new TransactionOptions();
- option.IsolationLevel = IsolationLevel.ReadCommitted;
- using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,option))
- {
- using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
- {
- conn.Open();
- SqlCommand cmd = new SqlCommand(conn);
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
- cmd.ExecuteNonQuery();
- }
- ts.Complete();
- }
- TransactionOptions option = new TransactionOptions();
- option.IsolationLevel = IsolationLevel.ReadCommitted;
- using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,option))
- {
- using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
- {
- conn.Open();
- SqlCommand cmd = new SqlCommand(conn);
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('测试2','2')";
- cmd.ExecuteNonQuery();
- }
- ts.Complete();
- }
4、EnterpriseServices实现事务
就是利用com+实现自动处理事务。
添加引用System.EnterpriseServices.dll
using System.EnterpriseServices;
使用方法参考:http://support.microsoft.com/default.aspx?scid=kb;zh-cn;816141
随便建立一个按钮,在按钮中进行如下操作:
- try
- {
- work1();
- work2();
- ContextUtil.SetComplete();
- }
- catch(System.Exception except)
- {
- ContextUtil.SetAbort();
- Response.Write(except.Message);
- }
- try
- {
- work1();
- work2();
- ContextUtil.SetComplete();
- }
- catch(System.Exception except)
- {
- ContextUtil.SetAbort();
- Response.Write(except.Message);
- }
然后在页面中添加2个操作,模拟一下在逻辑层调用不同类中的操作的情况 :
- private void work1()
- {
- SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["conn"]);
- SqlCommand cmd1=new SqlCommand("Insert Into trantest (id,test)values(1,'test')",conn);
- conn.Open();
- cmd1.ExecuteNonQuery();
- conn.Close();
- }
- private void work2()
- {
- SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["conn"]);
- SqlCommand cmd2=new SqlCommand("Insert Into trantest (id,test)values(2,'test')",conn);
- conn.Open();
- cmd2.ExecuteNonQuery();
- conn.Close();
- }
- 修改前台页面在<%Page后面添加 Transaction="Required" 即可
C#中分布式事务的超时处理问题的更多相关文章
- Google关于Spanner的论文中分布式事务的实现
Google关于Spanner的论文中分布式事务的实现 Google在Spanner相关的论文中详细的解释了Percolator分布式事务的实现方式, 而且用简洁的伪代码示例怎么实现分布式事务; Pe ...
- Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案
Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案 说明:Java生鲜电商平台中由于采用了微服务架构进行业务的处理,买家,卖家,配送,销售,供应商等进行服务化,但是不可避免存在 ...
- ASP.NET中分布式事务的使用
之前发表了一篇事务的存储过程,最近在做项目的时候遇到分布式事务,所有总结一下,跟大家分享和交流一下经验.首先说明为什么要分布式事务呢?先说说我在项目的哪里遇到分布式事务吧,我是在做网站后台开发的时候, ...
- j2ee中spring的分布式事务实现及解决方案
1 java事务类型 Java事务的类型有三种:JDBC事务.JTA(Java Transaction API)事务.容器事务. 常见的容器事务如Spring事务,容器事务主要是J2EE应用服务器提供 ...
- MySQL 中基于 XA 实现的分布式事务
1 XA协议 首先我们来简要看下分布式事务处理的XA规范可知XA规范中分布式事务有AP,RM,TM组成: 其中应用程序(Application Program ,简称AP):AP定义事务边界(定义事务 ...
- Mycat 分布式事务的实现
引言:Mycat已经成为了一个强大的开源分布式数据库中间件产品.面对企业应用的海量数据事务处理,是目前最好的开源解决方案.但是如果想让多台机器中的数据保存一致,比较常规的解决方法是引入"协调 ...
- 微服务分布式事务之LCN、TCC
在亿级流量架构之分布式事务解决方案对比中, 已经简单阐明了从本机事务到分布式事务的演变过程, 文章的最后简单说明了TCC事务, 这儿将会深入了解TCC事务是原理, 以及理论支持, 最后会用Demo举例 ...
- 分布式事务与Seate框架(1)——分布式事务理论
前言 虽然在实际工作中,由于公司与项目规模限制,实际上所谓的微服务分布式事务都不会涉及,更别提单独部署构建Seata集群.但是作为需要不断向前看的我,还是有必要记录下相关的分布式事务理论与Seate框 ...
- oracle分布式事务总结-转载
基本概念 Local Coordinator:在分布事务中,必须参考其它节点上的数据才能完成自己这部分操作的站点. Global Coordinator:分布事务的发起者,负责协调这个分布事务. Co ...
随机推荐
- 还是只使用console.log()进行调试?好吧,其实还有更多。
在浏览器控制台中打印消息无疑可以拯救所有开发人员. console.log()消息就像您的大多数疾病的药,同时调试了代码中的一些有线问题. 那里的大多数开发人员都喜欢— 让我们在浏览器中打印消息以了解 ...
- sql 语句系列(字符串的遍历嵌入删除与统计)[八百章之第十一章]
遍历字符串 我觉得首先要提出一个疑问: 一个数据库本身就是用于存储的,遍历字符串究竟有何意义? 先看如何实现的,毕竟sql service 是没有for循环,也没有loop和while的. selec ...
- 破解WIFI教程
今日主题:如何破解WIFI 准备工具 笔记本一台 usb无线网卡[我用的是小米的] kali系统[可以在虚拟机里装,建议用2019年及以下版本] VMware Workstation15虚拟机安装 可 ...
- vnpy源码阅读学习(7):串在一起
串在一起 我们已经分析了UI.MainEngine.EventEngine.然后他们几个是如何发挥作用的呢?我总结了一张图: 我们来具体的看看UI部分是如何跟EventEngine穿插起来的 \exa ...
- node 模块载入原理【1】
简单介绍 我们会从简单的模块载入原理来开始,尝试阅读下 Node.js 源代码.首先我们知道 Node.js 的源代码主要是由 C++ 和 JavaScript 编写的,JS 部分主要在 lib 目录 ...
- 将Mongodb的表导入到Hive中
1.官方文档:https://docs.mongodb.com/ecosystem/tools/hadoop/ 2.Hive介绍: Hive特点: 1.hive是一个数据仓库,和oracle,mysq ...
- 你所不知道的 C# 中的细节
前言 有一个东西叫做鸭子类型,所谓鸭子类型就是,只要一个东西表现得像鸭子那么就能推出这玩意就是鸭子. C# 里面其实也暗藏了很多类似鸭子类型的东西,但是很多开发者并不知道,因此也就没法好好利用这些东西 ...
- PS2手柄在arduino上进行测试,可用,供喜欢diy的朋友借鉴
#include <PS2X_lib.h> //PS2手柄PS2X ps2x; // create PS2 Controller Class//////////PS2引脚///////// ...
- redis的使用及配置
linux环境下redis启动和管理 在redis根目录下执命令 快捷启动默认端口 ./redis-server ../redis.conf 启动redis管理端 ./redis-cli 清理缓存命令 ...
- 深入理解Java AIO(一)—— Java AIO的简单使用
深入理解Java AIO(一)—— Java AIO的简单使用 深入理解AIO系列分为三个部分 第一部分也就是本节的Java AIO的简单使用 第二部分是AIO源码解析(只解析关键部分)(待更新) 第 ...