首先,您必须将在这里和那里读到的有关SQL Server事务的内容分为两种不同的情况:本地和分布式。
本地SQL事务 :
- SQL Server只允许在每个本地事务上执行一个请求。
- 默认情况下,只有一个会话可以注册本地事务。使用sp_getbindtoken和sp_bindsession可以将多个会话注册到本地事务中。会话仍然限于在任何时间仅执行一个请求。
- 使用多个活动结果集(MARS),一个会话可以执行多个请求。所有请求都必须注册在同一本地事务中。
分布式事务 :
- 多个会话可以将其本地事务注册到单个分布式事务中。
- 每个会话仍在本地交易中注册,但要遵守上述本地交易的所有限制
- 分布式事务中注册的本地事务受分布式事务协调的两阶段提交的约束
- 注册到分布式事务中的实例上的所有本地事务仍然是 独立的 本地事务,这主要意味着它们具有冲突的锁命名空间。
因此,当客户端创建.Net
TransactionScope并在此事务范围下,它在同一服务器上执行多个请求时,这些请求都是注册在分布式事务中的所有本地事务。一个简单的例子:
class Program { static string sqlBatch = @"set nocount on;declare @i int;set @i = 0;while @i < 100000begin insert into test (a) values (replicate('a',100)); set @i = @i+1;end"; static void Main(string[] args) { try { TransactionOptions to = new TransactionOptions(); to.IsolationLevel = IsolationLevel.ReadCommitted; using (TransactionScope scp = new TransactionScope(TransactionScopeOption.Required, to)) { using (SqlConnection connA = new SqlConnection(Settings.Default.connString)) { connA.Open(); using (SqlConnection connB = new SqlConnection(Settings.Default.connString)) { connB.Open(); SqlCommand cmdA = new SqlCommand(sqlBatch, connA); SqlCommand cmdB = new SqlCommand(sqlBatch, connB); IAsyncResult arA = cmdA.BeginExecuteNonQuery(); IAsyncResult arB = cmdB.BeginExecuteNonQuery(); WaitHandle.WaitAll(new WaitHandle[] { arA.AsyncWaitHandle, arB.AsyncWaitHandle }); cmdA.EndExecuteNonQuery(arA); cmdB.EndExecuteNonQuery(arB); } } scp.Complete(); } } catch (Exception e) { Console.Error.Write(e); } } }创建一个虚拟测试表:
create table test (id int not null identity(1,1) primary key, a varchar(100));
并运行示例中的代码。您将看到两个请求并行执行,每个请求浪费表中的100k行,然后在事务范围完成时都提交。因此,您看到的问题与SQL
Server或TransactionScope都不相关,它们可以轻松处理您描述的情况。而且,该代码非常简单明了,并且不需要创建依赖事务,进行克隆或促进事务。
更新
使用显式线程和相关事务:
private class ThreadState { public DependentTransaction Transaction {get; set;} public EventWaitHandle Done {get; set;} public SqlConnection Connection { get; set; } } static void Main(string[] args) { try { TransactionOptions to = new TransactionOptions(); to.IsolationLevel = IsolationLevel.ReadCommitted; using (TransactionScope scp = new TransactionScope(TransactionScopeOption.Required, to)) { ThreadState stateA = new ThreadState { Transaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete), Done = new AutoResetEvent(false), Connection = new SqlConnection(Settings.Default.connString), }; stateA.Connection.Open(); ThreadState stateB = new ThreadState { Transaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete), Done = new AutoResetEvent(false), Connection = new SqlConnection(Settings.Default.connString), }; stateB.Connection.Open(); ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), stateA); ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), stateB); WaitHandle.WaitAll(new WaitHandle[] { stateA.Done, stateB.Done }); scp.Complete(); //TODO: dispose the open connections } } catch (Exception e) { Console.Error.Write(e); } } private static void Worker(object args) { Debug.Assert(args is ThreadState); ThreadState state = (ThreadState) args; try { using (TransactionScope scp = new TransactionScope(state.Transaction)) { SqlCommand cmd = new SqlCommand(sqlBatch, state.Connection); cmd.ExecuteNonQuery(); scp.Complete(); } state.Transaction.Complete(); } catch (Exception e) { Console.Error.WriteLine(e); state.Transaction.Rollback(); } finally { state.Done.Set(); } }


