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项目,拿到的 ...
随机推荐
- jQuery设置和获取HTML、文本和值
jQuery设置和获取HTML.文本和值 按 Ctrl+C 复制代码 <script type="text/javascript"> //<![CDATA[ $( ...
- 描述JSP和Servlet的区别、共同点、各自应用的范围
描述JSP和Servlet的区别.共同点.各自应用的范围 解答:JSP在本质上就是SERVLET,但是两者的创建方式不一样.Servlet完全是JAVA程序代码构成,擅长于流程控制和事务处理,通过Se ...
- HDU-1095-A+B for Input-Output Practice (VII)(多一个空格?)
A+B for Input-Output Practice (VII) Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32 ...
- ios 页面过长卡顿的情况
解决方案如下: -webkit-overflow-scrolling: touch;
- _beginthreadex创建线程,立即执行?
一个线程创建后,并不是立马就执行,而是等时间片到来后才执行... C++ Code 12345678910111213141516171819202122232425262728293031323 ...
- Django实现组合搜索的方法示例
目录 一.实现方法 二.基本原理 三.代码样例 方法1:纯模板语言实现 方法二:使用simpletag实现 四.其他变化 1.model定义 2.处理函数变化 3.simpletag相应改变 一. ...
- 升级个人网站框架组件IBatisNet+Castle
<sqlMap namespace="Sequence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance& ...
- 如何阅读不同格式的Ubuntu/Linux帮助文档
Ubuntu和Linux的帮助文档有各种各样的格式,下面简单说下如何阅读这些帮助文档. 1)通过man命令阅读软件或命令的manual page.例如阅读man命令的manual页面可使用如下命令: ...
- 【BZOJ2124】等差子序列 树状数组维护hash值
[BZOJ2124]等差子序列 Description 给一个1到N的排列{Ai},询问是否存在1<=p1<p2<p3<p4<p5<…<pLen<=N ...
- 禁用chrome默认打印框直接打印
在做web打印的时候 重要弹出一个对话框选择打印机等等, 比较影响操作, 经过一番谷歌终于找到了解决方案 在桌面的chrome 图标 右键 打开属性对话框 在目标 添加 --kiosk-printi ...