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项目,拿到的 ...
随机推荐
- c#检查网络文件是否存在
public bool IsExist(string uri) { HttpWebRequest req = null; HttpWebResponse res = null; try { req = ...
- 【python】获取网页中中文内容并分词
# -*- coding: utf-8 -*- import urllib2 import re import time import jieba url="http://www.baidu ...
- sql查询某个字段最长的记录
select `字段`, length(`字段`) from 表名 where length(`字段`) = ( select max(length(`字段`)) from 表名 )
- 深入java虚拟机(一) 虚拟机内存结构
java虚拟机所管理的内存区域(运行时数据区)主要分为如下几个部分:堆(heap).方法区(method area).虚拟机栈(VM stack).本地方法栈(native method stack) ...
- JZOJ.5275【NOIP2017模拟8.14】水管
Description
- JavaScript-烂笔头
JavaScript 对大小写敏感 注释单行用:// 注释多汗用:/* */ 声明变量:var 变量名 (未使用值来声明的变量,值为undefined) JavaScript 变量均为对象 可以使用关 ...
- 巨蟒python全栈开发-第4天 列表&元组&range
今日内容大纲 1. 什么是列表 定义: 能装对象的对象 在python中使用[]来描述列表, 内部元素用逗号隔开. 对数据类型没有要求 列表存在索引和切片. 和字符串是一样的. 2. 相关的增删改查操 ...
- 机器被感染病毒文件zigw的处理流程
1.现象 服务器CPU报警,查看时,已接近100%. 2.查找 使用top查看是哪个进程在占用CPU,此时zigw立刻出现,记录下进程的PID,假如为12345. (1) 如果在不知道程序的路径前,就 ...
- pro-select-like
DELIMITER | drop procedure if exists pro_query; CREATE PROCEDURE pro_query ( cname VARCHAR() ) BEGIN ...
- 邮件发送异常, [Errno 110] Connection timed out
邮件发送异常, [Errno 110] Connection timed out SMTP 服务地址(华东 1): smtpdm.aliyun.com SMTP 服务地址(新加坡):smtpdm-a ...