.NET简谈事务、分布式事务处理
在本人的 “ .NET简谈事务本质论”一文中我们从整体上了解了事务模型,在我们脑子里能有一个全局的事务处理结构,消除对数据库事务的依赖理解,重新认识事务编程模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | insert into test values ( '222' ) --我们在表test中插入一条记录 go begin transaction tr begin try begin insert into test2 values ( '111' ) insert into test values ( '222' ) --该行插入会导致主键冲突,也就是我们要的效果 end commit transaction tr end try begin catch print '事务执行错误!' print error_number() rollback transaction tr end catch |
例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class Test { SqlConnection conn = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); public void Add() { conn.Open(); SqlCommand command = new SqlCommand( "insert into test2 values(111)" , conn); try { command.Transaction = conn.BeginTransaction(); command.ExecuteNonQuery(); command.CommandText = "insert into test values(222)" ; command.ExecuteNonQuery(); command.Transaction.Commit(); } catch (Exception err) { Console.WriteLine(err); command.Transaction.Rollback(); } } } |
这就是典型ADO.NET事务处理代码,其实和我们第一个例子中的T-SQL代码是差不多的,通过ADO.NET中的SqlConnection.BeginTransaction()获取到对底层ODBC中的数据库事务的引用,其实这里还没有真正的设计到.NET中的事务处理代码,这里只是对数据库管理系统的远程调用,通过远程处理的消息通讯进行事务处理远程化。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class DisplayTransactioninfo { public static void Display(System.Transactions.Transaction tr) { if (tr != null ) { Console.WriteLine( "Createtime:" + tr.TransactionInformation.CreationTime); Console.WriteLine( "Status:" + tr.TransactionInformation.Status); Console.WriteLine( "Local ID:" + tr.TransactionInformation.LocalIdentifier); Console.WriteLine( "Distributed ID:" + tr.TransactionInformation.DistributedIdentifier); Console.WriteLine(); } } |
CommittableTransaction事务处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class Test3 { SqlConnection conn; CommittableTransaction committran = new CommittableTransaction(); public Test3() { conn = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); DisplayTransactioninfo.Display(committran); } public void Add3() { conn.Open(); conn.EnlistTransaction(committran); //需要将本次的连接操作视为事务性的 SqlCommand command = new SqlCommand(); try { command.Connection = conn; command.CommandText = "insert into test2 values(111)" ; command.ExecuteNonQuery(); command.CommandText = "insert into test values(222)" ; command.ExecuteNonQuery(); committran.Commit(); } catch (Exception err) { committran.Rollback(); //出现出错执行回滚操作} } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | [System.Runtime.InteropServices.ComVisible( true )] //COM+是在COM的基础上发展起来的,需要将.NET程序集中的类型公开为COM组件。 [System.EnterpriseServices.Transaction(TransactionOption.Required)] //始终需要事务处理域 public class Test2 : ServicedComponent { public Test2() { } SqlConnection conn = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); [AutoComplete( true )] //如果在方法的执行体类没有出现错误,那么将自动设置事务处理的结果 public void Add2() { conn.Open(); SqlCommand command = new SqlCommand(); try { command.Connection = conn; command.CommandText = "insert into test2 values(111)" ; command.ExecuteNonQuery(); command.CommandText = "insert into test values(222)" ; command.ExecuteNonQuery(); } catch { System.EnterpriseServices.ContextUtil.SetAbort(); } } } |
DependentTransaction跨线程事务处理
我们在编写高并发量程序时,都会用到多线程来进行处理,让主线程能有时间来处理第一线的请求,然后将请求分发到各个子线程上进行后台的处理。我们来看一幅图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class Test6 { CommittableTransaction commit = new CommittableTransaction(); SqlConnection conn1 = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); public Test6() { conn1.Open(); conn1.EnlistTransaction(commit); } public void Add6() { try { DisplayTransactioninfo.Display(commit); SqlCommand command = new SqlCommand( "insert into test2 values(111)" , conn1); command.ExecuteNonQuery(); Thread thread = new Thread(Test6.CommitThread); thread.Start(commit.DependentClone(DependentCloneOption.BlockCommitUntilComplete)); commit.Commit(); } catch (Exception err) { commit.Rollback(); } } public static void CommitThread( object co) { DependentTransaction commit = co as DependentTransaction; SqlConnection conn2 = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); conn2.Open(); conn2.EnlistTransaction(commit as DependentTransaction); DisplayTransactioninfo.Display(commit); SqlCommand command = new SqlCommand( "insert into test values(111)" , conn2); try { command.ExecuteNonQuery(); commit.Complete(); } catch (Exception err) { Console.WriteLine(err); commit.Rollback(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class Test4 { SqlConnection conn1 = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); SqlConnection conn2 = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); CommittableTransaction committran = new CommittableTransaction(); public Test4() { DisplayTransactioninfo.Display(committran); conn1.Open(); conn1.EnlistTransaction(committran); conn2.Open(); conn2.EnlistTransaction(committran); DisplayTransactioninfo.Display(committran); } public void Add4() { try { SqlCommand command1 = new SqlCommand( "insert into test2 values(111)" , conn1); command1.ExecuteNonQuery(); SqlCommand command2 = new SqlCommand( "insert into test values(222)" , conn2); command2.ExecuteNonQuery(); } catch (Exception err) { Console.WriteLine(err); committran.Rollback(); } } } |
1 2 3 4 5 6 7 8 | //服务契约(ServiceContract): [ServiceContract(SessionMode = SessionMode.Required)] public interface IDistributedTransaction { [TransactionFlow(TransactionFlowOption.Allowed)] [OperationContract] void Add(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //服务类1: public class DistributedTransactionService1 : IDistributedTransaction { SqlConnection conn1 = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); #region IDistributedTransaction [OperationBehavior(TransactionAutoComplete = true , TransactionScopeRequired = true )] public void Add() { conn1.Open(); SqlCommand command = new SqlCommand( "insert into test2 values(111)" , conn1); command.ExecuteNonQuery(); DataBaseOperation.DisplayTransactioninfo.Display(System.Transactions.Transaction.Current); } #endregion } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //服务类2: public class DistributedTransactionService2 : IDistributedTransaction { SqlConnection conn2 = new SqlConnection( "data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI" ); #region IDistributedTransaction [OperationBehavior(TransactionAutoComplete = true , TransactionScopeRequired = true )] public void Add() { conn2.Open(); SqlCommand command = new SqlCommand( "insert into test values(222)" , conn2); try { DataBaseOperation.DisplayTransactioninfo.Display(System.Transactions.Transaction.Current); command.ExecuteNonQuery(); } catch (Exception err) { throw err; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <service name= "ServerConsole.Transaction.DistributedTransactionService1" behaviorConfiguration= "metadatabehaviors" > <host> <baseAddresses> <add baseAddress= "http://localhost:8027" /> <add baseAddress= "net.tcp://localhost:8028" /> </baseAddresses> </host> <endpoint address= "DistributedTransactionService1" binding= "netTcpBinding" bindingConfiguration= "tranbinding" contract= "ServerConsole.Transaction.IDistributedTransaction" ></endpoint> </service> <service name= "ServerConsole.Transaction.DistributedTransactionService2" behaviorConfiguration= "metadatabehaviors" > <host> <baseAddresses> <add baseAddress= "http://localhost:8029" /> <add baseAddress= "net.tcp://localhost:8030" /> </baseAddresses> </host> <endpoint address= "DistributedTransactionService2" binding= "netTcpBinding" bindingConfiguration= "tranbinding" contract= "ServerConsole.Transaction.IDistributedTransaction" ></endpoint> </service> |
Binding配置:
1 2 3 4 5 6 7 | <bindings> <netTcpBinding> <binding name= "tranbinding" transactionFlow= "true" transactionProtocol= "WSAtomicTransactionOctober2004" > <reliableSession enabled= "true" ordered= "true" /> </binding> </netTcpBinding> </bindings> |
我们需要打开Binding的事务流传递。
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | DistributedTransactionClient.DistributedTransactionClient tranclient = new DistributedTransactionClient.DistributedTransactionClient(); DistributedTransaction2Client.DistributedTransactionClient tranclient2 = new DistributedTransaction2Client.DistributedTransactionClient(); using (TransactionScope transcope = new TransactionScope()) { try { Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted); tranclient.Add(); tranclient2.Add(); transcope.Complete(); } catch (Exception err) { Transaction.Current.Rollback(); } } static void Current_TransactionCompleted( object sender, TransactionEventArgs e) { if (e.Transaction.TransactionInformation.Status == System.Transactions.TransactionStatus.Committed) Console.WriteLine(e.Transaction.TransactionInformation.DistributedIdentifier); } |
客户端使用TransactionScope类来进行环境事务的设置,这样就很方便知道事务的执行范围,在TransactionScope里面我们可以通过Transaction.Current获取到当前上下文的事务对象,由于事务对象是存储在线程独立存储区里的,所以跨线程访问是没用的,通过依赖事务进行传递。[王清培版权所有,转载请给出署名]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!