C#中跨库事务处理解决方案
最近新接手了一项业务,其中有一个方法,需要对业务表进行写入数据,之后记录到日志表中。这部分代码原先是前人写的,他没有采用任何方案,只是简单的调用Ado.net执行了两次写库操作。因此经常出现系统使用者不断发邮件说数据有问题,经过查看原因就是在于写库操作中,有某个表写入失败,但是其他表写入成功,导致出现了数据不一致的问题。后来本想改用事务,但发现日志表和业务表不在同一个数据库下,甚至不在同一个IP下,对于这个问题,我想到了有以下解决方案。
由ado.net管理的事务改为自己手动提交事务和Commit或者RollBack操作:
step1:按照连接字符串和sql分类,存入Dictionary<string,string>中,Key为连接字符串,Value为针对此数据库的Sql语句,多条用分号隔开;
step2:遍历此Dictionary,打开这些连接;
step3:对于每个连接,打开事务;
step4:执行针对每个连接的sql,出现错误则全部rollback,否则全部commit;
step5:关闭连接,记录运行情况,记录日志。
具体代码如下:
//提交事务用的sql
public const string MultiTran = @"BEGIN TRAN
{0}"; /// <summary>
/// 事务返回的信息
/// </summary>
public struct TransInfo
{
/// <summary>
/// sql总条数
/// </summary>
public int Total;
/// <summary>
/// 事务执行是否成功
/// </summary>
public bool IsSuccess;
/// <summary>
/// 失败时的sql
/// </summary>
public string WrongMessage;
} /// <summary>
/// 跨库事务异常对象
/// </summary>
public class TransException : Exception
{
public TransException(string message) : base(message)
{
} public string wrongSQL { get; set; }
public string wrongAt { get; set; }
/// <summary>
/// 已经打开的连接
/// </summary>
public List<SqlConnection> DoneConnection = new List<SqlConnection>();
/// <summary>
/// 出现错误的连接
/// </summary>
public SqlConnection CurrentConnection;
/// <summary>
/// 覆盖Exception中的Message字段,使其可写
/// </summary>
public new string Message { get; set; }
} /// <summary>
/// 多操作sql,使用事务,用于多库事务
/// <para>
/// 返回值TransInfo字段:IsSuccess 是否成功,
/// Total sql总条数,
/// WrongAt 失败的sql语句
/// </para>
/// </summary>
/// <param name="sqlwithconn">执行的sql和连接字符串列表key:sql,value:连接字符串</param>
/// <param name="connectionString">连接字符串</param>
/// <returns>sadf</returns>
public static TransInfo RunSqlInTrans(Dictionary<string, string> sqlwithconn)
{
var sqltable = new Dictionary<string, string>();
var conntable = new Dictionary<string, SqlConnection>(); foreach (var i in sqlwithconn)
{
if (!sqltable.Keys.Contains(i.Value))
{
sqltable.Add(i.Value, i.Key); //sqltable的key是连接字符串,value是sql语句
conntable.Add(i.Value, new SqlConnection(i.Value)); //key是连接字符串,value是连接对象
}
else
{
sqltable[i.Value] += ";" + i.Key;
}
} try
{
var wrongEx = new TransException("");
foreach (var i in sqltable)
{
//遵照晚开早关原则,在此处打开数据库连接
conntable[i.Key].Open();
//连接打开后,将连接对象放入异常处理对象中做记录
wrongEx.DoneConnection.Add(conntable[i.Key]);
var dc = new SqlCommand(string.Format(MultiTran, i.Value), conntable[i.Key]);
try
{
dc.ExecuteNonQuery();
}
catch (Exception ex)
{
//出现异常,抛出异常处理对象
wrongEx.CurrentConnection = conntable[i.Key];
wrongEx.wrongAt = i.Key;
wrongEx.wrongSQL = sqltable[i.Key];
wrongEx.Message = ex.Message;
throw wrongEx;
}
}
//全部执行完毕没有发现错误,提交事务
foreach (var i in conntable)
{
var dc = new SqlCommand("COMMIT TRAN", i.Value);
dc.ExecuteNonQuery();
i.Value.Close();
}
return new TransInfo()
{
IsSuccess = true,
Total = sqlwithconn.Count,
WrongMessage = ""
}; }
catch (TransException e) //1.回滚所有操作2.关闭所有已经打开的数据库连接4.生成错误对象
{
foreach (var i in e.DoneConnection)
{
if (!i.Equals(e.CurrentConnection))
{
var dc = new SqlCommand("ROLLBACK TRAN", i);
dc.ExecuteNonQuery();
}
i.Close();
}
return new TransInfo()
{
IsSuccess = false,
Total = sqlwithconn.Count,
WrongMessage = string.Format("在连接{0}中,操作{1}出现错误,错误信息:{2}", e.wrongAt, e.wrongSQL, e.Message)
};
}
}
这样解决了跨库数据表处理有时因为网络问题或其他偶然性问题导致的数据不一致的问题。但是这个解决方案最大的问题就是在于性能问题上,比如如果有多个库假设为A,B,C,D,其中C库的数据修改写入比较复杂,那么在A,B库开启事务后,必须等待C和D库完成或失败后,事务才可以结束,连接才能释放,这个时候,A库和B库就是处于挂起状态,如果处于高IO的生产环境中的话,这个性能的损失可能是致命的,所以这个方案只能用于简单的sql处理,而且处理sql不能太多或者太复杂。而且出现网络波动的话,损失会更大。幸运的是我所接手的这个业务,是在内网环境中,同时只用两句sql在两个库中,所以用这个方案问题不大。
总结:针对这个问题,我认为当初设计数据库时,能避免跨库就一定要避免。
如果大家有什么更好的解决方案的话,希望和大家多多交流和指教。
C#中跨库事务处理解决方案的更多相关文章
- PostgreSQL数据库中跨库访问解决方案
PostgreSQL跨库访问有3种方法:Schema,dblink,postgres_fdw. 方法A:在PG上建立不同SCHEMA,将数据和存储过程分别放到不同的schema上,经过权限管理后进行访 ...
- jquery 与其他库冲突解决方案
var j = jQuery.noConflict(); j("div p").hide(); // 基于 jQuery 的代码 $("content").st ...
- postgreSQL中跨库查询在windows下的实现方法
以下是在postgreSQL 8.1版本中的实践,其他版本类似: 1.将C:\Program Files\PostgreSQL\8.1\share\contrib下的dblink.sql复制到C:\P ...
- Python pip包管理器安装第三方库超时解决方案
一.国内镜像安装 使用方法:pip install --index 镜像网站 第三方库名 二.镜像网站 http://pypi.douban.com/simple/ 豆瓣 http://mirrors ...
- Vue中跨域问题解决方案1
我们需要配置代理.代理可以解决的原因:因为客户端请求服务端的数据是存在跨域问题的,而服务器和服务器之间可以相互请求数据,是没有跨域的概念(如果服务器没有设置禁止跨域的权限问题),也就是说,我们可以配置 ...
- SQLServer中跨库复制数据
SQLServer中把某个表里的记录复制到另一个数据库的表中的操作方法. 场景 现有数据库a和数据库b,数据库a里有表table1,数据库b里有表table2.现在要把表table1里的记录复制到ta ...
- 详解EBS接口开发之库事务处理带提前发运通知(ASN)采购接收入库-补充
A) Via ROI Create a ASN [ship,ship] for a quantity =3 on STANDARD PURCHASE ORDER Create via R ...
- C/C++ 跨平台交叉编译、静态库/动态库编译、MinGW、Cygwin、CodeBlocks使用原理及链接参数选项
目录 . 引言 . 交叉编译 . Cygwin简介 . 静态库编译及使用 . 动态库编译及使用 . MinGW简介 . CodeBlocks简介 0. 引言 UNIX是一个注册商标,是要满足一大堆条件 ...
- Android JNI如何调用第三方库
http://www.2cto.com/kf/201504/388764.html Android JNI找不到第三方库的解决方案 cannot load library 最近做一个jni项目,拿到的 ...
随机推荐
- hdu1027(n个数的按字典序排列的第m个序列)
题目信息:给出n.m,求n个数的按字典序排列的第m个序列 http://acm.hdu.edu.cn/showproblem.php? pid=1027 AC代码: /** *全排列的个数(次序) ...
- 【机器学习】WIFI室内定位
WIFI室内定位-指纹法 在A1区域内每个点上采集四个WiFi的信号数据(信号强度),五点.九点.十六点采样. 5*5=25区域*16数据=400样本,用来训练 样本数 R B G1 G2 1 2 ...
- python入门(五):面向对象
面向对象术语 类(Class): 用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法.对象是类的实例. 类变量:类变量在整个实例化的对象中是公用的.类变量定义在类中且 ...
- python3----练习题(冒泡排序)
冒泡,原理是临近的数字两两进行比较,按照从小到大的顺序进行交换,这样交换一次之后,最大的数字就被交换到了最后一位. li = [33, 2, 10, 1] for j in range(1, len( ...
- Extjs4 中date时间格式的问题
在Grid中显示时间,后台传过来的是date格式的数据(PHP date('Y-m-d', time()),一般在Ext model中定义数据的类型和格式: {name:'birth', type:' ...
- AndroidManifest.xml文件详解(activity)(二)
android:configChanges 这个属性列出了那些需要Activity进行自我处理的配置变化.当在运行时配置变化发生的时候,默认情况下,这个Activity会被关掉并重启,但是用这个属性声 ...
- 初步了解 cURL
今天需要用PHP模拟post请求,查了查资料,了解到cURL.看了一篇博客,写的很详细,就转载了,与大家分享.[原文链接] 什么是cURL?可能还有很多同学没有听说过这个工具,我先来给大家简单介绍下什 ...
- golang build 编译规则
文章来源: http://blog.csdn.net/varding/article/details/12675971 讲述了golang中的条件编译,摘要如下: 第一种条件编译的方法:编译标签 编译 ...
- linux系统中利用vagrant创建虚拟开发环境
Vagrant简介 作为程序员,可能需要同时开发多个项目,使用多种编程语言,需要使用各种操作系统,如果将很多东西放在同一个电脑上,肯定会被各种配置环境搞晕.一个比较好的办法就是每个项目都有一个干净的开 ...
- python迭代器、生成器、yield和xrange
https://blog.csdn.net/u010138758/article/details/56291013