Во-первых, вы должны разделить то, что читали здесь и там о транзакциях SQL Server, на 2 разных случая: локальный и распределенный.
Локальные транзакции 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 < 100000
begin
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));
и запустите код в моем примере.Вы увидите, что оба запроса выполняются параллельно, каждый из которых извлекает 100 тыс. Строк в таблице, а затем оба фиксируют, когда завершается область транзакции.Таким образом, проблемы, с которыми вы сталкиваетесь, не связаны ни с 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();
}
}