Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 660|回复: 0

C#中跨库事务处理解决方案

[复制链接]
  • TA的每日心情
    奋斗
    5 天前
  • 签到天数: 802 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726006
    发表于 2021-6-10 11:39:12 | 显示全部楼层 |阅读模式

      最近新接手了一项业务,其中有一个方法,需要对业务表进行写入数据,之后记录到日志表中。这部分代码原先是前人写的,他没有采用任何方案,只是简单的调用Ado.net执行了两次写库操作。因此经常出现系统使用者不断发邮件说数据有问题,经过查看原因就是在于写库操作中,有某个表写入失败,但是其他表写入成功,导致出现了数据不一致的问题。后来本想改用事务,但发现日志表和业务表不在同一个数据库下,甚至不在同一个IP下,对于这个问题,我想到了有以下解决方案。

    由ado.net管理的事务改为自己手动提交事务和Commit或者RollBack操作:

    step1:按照连接字符串和sql分类,存入Dictionary<string,string>中,Key为连接字符串,Value为针对此数据库的Sql语句,多条用分号隔开;

    step2:遍历此Dictionary,打开这些连接;

    step3:对于每个连接,打开事务;

    step4:执行针对每个连接的sql,出现错误则全部rollback,否则全部commit;

    step5:关闭连接,记录运行情况,记录日志。

    具体代码如下:

      1         //提交事务用的sql
      2         public const string MultiTran = @"BEGIN TRAN
      3                                             {0}";
      4 
      5         /// <summary>
      6         /// 事务返回的信息
      7         /// </summary>
      8         public struct TransInfo
      9         {
     10             /// <summary>
     11             /// sql总条数
     12             /// </summary>
     13             public int Total;
     14             /// <summary>
     15             /// 事务执行是否成功
     16             /// </summary>
     17             public bool IsSuccess;
     18             /// <summary>
     19             /// 失败时的sql
     20             /// </summary>
     21             public string WrongMessage;
     22         }        
     23 
     24         /// <summary>
     25         /// 跨库事务异常对象
     26         /// </summary>
     27         public class TransException : Exception
     28         {
     29             public TransException(string message) : base(message)
     30             {
     31             }
     32 
     33             public string wrongSQL { get; set; }
     34             public string wrongAt { get; set; }
     35             /// <summary>
     36             /// 已经打开的连接
     37             /// </summary>
     38             public List<SqlConnection> DoneConnection = new List<SqlConnection>();
     39             /// <summary>
     40             /// 出现错误的连接
     41             /// </summary>
     42             public SqlConnection CurrentConnection;
     43             /// <summary>
     44             /// 覆盖Exception中的Message字段,使其可写
     45             /// </summary>
     46             public new string Message { get; set; }
     47         }
     48 
     49         /// <summary>
     50         /// 多操作sql,使用事务,用于多库事务
     51         /// <para>
     52         /// 返回值TransInfo字段:IsSuccess 是否成功,
     53         /// Total sql总条数,
     54         /// WrongAt 失败的sql语句
     55         /// </para>
     56         /// </summary>
     57         /// <param name="sqlwithconn">执行的sql和连接字符串列表key:sql,value:连接字符串</param>
     58         /// <param name="connectionString">连接字符串</param>
     59         /// <returns>sadf</returns>
     60         public static TransInfo RunSqlInTrans(Dictionary<string, string> sqlwithconn)
     61         {
     62             var sqltable = new Dictionary<string, string>();
     63             var conntable = new Dictionary<string, SqlConnection>();
     64 
     65             foreach (var i in sqlwithconn)
     66             {
     67                 if (!sqltable.Keys.Contains(i.Value))
     68                 {
     69                     sqltable.Add(i.Value, i.Key); //sqltable的key是连接字符串,value是sql语句
     70                     conntable.Add(i.Value, new SqlConnection(i.Value));    //key是连接字符串,value是连接对象
     71                 }
     72                 else
     73                 {
     74                     sqltable[i.Value] += ";" + i.Key;
     75                 }
     76             }
     77 
     78             try
     79             {
     80                 var wrongEx = new TransException("");
     81                 foreach (var i in sqltable)
     82                 {
     83                     //遵照晚开早关原则,在此处打开数据库连接
     84                     conntable[i.Key].Open();
     85                     //连接打开后,将连接对象放入异常处理对象中做记录
     86                     wrongEx.DoneConnection.Add(conntable[i.Key]);
     87                     var dc = new SqlCommand(string.Format(MultiTran, i.Value), conntable[i.Key]);
     88                     try
     89                     {
     90                         dc.ExecuteNonQuery();
     91                     }
     92                     catch (Exception ex)
     93                     {
     94                         //出现异常,抛出异常处理对象
     95                         wrongEx.CurrentConnection = conntable[i.Key];
     96                         wrongEx.wrongAt = i.Key;
     97                         wrongEx.wrongSQL = sqltable[i.Key];
     98                         wrongEx.Message = ex.Message;
     99                         throw wrongEx;
    100                     }
    101                 }
    102                 //全部执行完毕没有发现错误,提交事务
    103                 foreach (var i in conntable)
    104                 {
    105                     var dc = new SqlCommand("COMMIT TRAN", i.Value);
    106                     dc.ExecuteNonQuery();
    107                     i.Value.Close();
    108                 }
    109                 return new TransInfo()
    110                 {
    111                     IsSuccess = true,
    112                     Total = sqlwithconn.Count,
    113                     WrongMessage = ""
    114                 };
    115 
    116             }
    117             catch (TransException e)   //1.回滚所有操作2.关闭所有已经打开的数据库连接4.生成错误对象
    118             {
    119                 foreach (var i in e.DoneConnection)
    120                 {
    121                     if (!i.Equals(e.CurrentConnection))
    122                     {
    123                         var dc = new SqlCommand("ROLLBACK TRAN", i);
    124                         dc.ExecuteNonQuery();
    125                     }
    126                     i.Close();
    127                 }
    128                 return new TransInfo()
    129                 {
    130                     IsSuccess = false,
    131                     Total = sqlwithconn.Count,
    132                     WrongMessage = string.Format("在连接{0}中,操作{1}出现错误,错误信息:{2}", e.wrongAt, e.wrongSQL, e.Message)
    133                 };
    134             }
    135         }

     这样解决了跨库数据表处理有时因为网络问题或其他偶然性问题导致的数据不一致的问题。但是这个解决方案最大的问题就是在于性能问题上,比如如果有多个库假设为A,B,C,D,其中C库的数据修改写入比较复杂,那么在A,B库开启事务后,必须等待C和D库完成或失败后,事务才可以结束,连接才能释放,这个时候,A库和B库就是处于挂起状态,如果处于高IO的生产环境中的话,这个性能的损失可能是致命的,所以这个方案只能用于简单的sql处理,而且处理sql不能太多或者太复杂。而且出现网络波动的话,损失会更大。幸运的是我所接手的这个业务,是在内网环境中,同时只用两句sql在两个库中,所以用这个方案问题不大。

    总结:针对这个问题,我认为当初设计数据库时,能避免跨库就一定要避免。

    如果大家有什么更好的解决方案的话,希望和大家多多交流和指教。

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-11-16 13:28 , Processed in 4.104656 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表