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项目,拿到的 ...
随机推荐
- .NET学习笔记(2)
--在子页面设置模板页的图片: ( this.Master.FindControl(“imgHead”) as Image ).ImageUrl = “upload/image1.jpg” ; 文件的 ...
- Git进一步学习
Git 安装配置 在使用Git前我们需要先安装 Git.Git 目前支持 Linux/Unix.Solaris.Mac和 Windows 平台上运行. Git 各平台安装包下载地址为:http://g ...
- unity 打开文件夹并鼠标选中某文件
System.Diagnostics.Process p = new System.Diagnostics.Process(); p.StartInfo.FileName = "explor ...
- hdu 4568(SPFA预处理+TSP)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4568 思路:先用spfa预处理出宝藏与宝藏之间的最短距离,宝藏到边界的最短距离,然后就是经典的求TSP ...
- python3----函数、匿名函数
本节将学习如何用Python定义函数,调用函数,以及学习匿名函数的使用 1.定义函数 Python中定义函数用关键字def,如下例所示,func为函数名 def func(): print( &quo ...
- Net应用架构设计
N-Tier 是从架构更大的维度上划分,每一个维度都是一个Tier(在微软的ESP2.0里翻译为”级”),比如电商架构划分如下: UI 服务接口 消息.缓存中间件 数据库 ...... Tier与Ti ...
- mysql中排序
排序(默认:asc升序; desc降序 如:根据成绩从高到低排序 select * from stu_info order by mark desc; 根据成绩从低到高排序 select * from ...
- 相似性分析之Jaccard相似系数
Jaccard, 又称为Jaccard相似系数(Jaccard similarity coefficient)用于比较有限样本集之间的相似性与差异性.Jaccard系数值越大,样本相似度越高 公式: ...
- SOE不能进入断点调试
一.前言 任何程序开发,如果不能进入断点调试,是非常的痛苦的. 如果有过SOE开发经验的人都知道,SOE开发过程中调试是非常麻烦的.任何在SOE开发模板中的修改都需要重新编译工程,重新生成.soe 文 ...
- django模板复用 extends,block,include
template复用 extends block include render 参考:https://code.ziqiangxuetang.com/django/django-template.ht ...