Как распределенные транзакции ведут себя с несколькими подключениями к одной и той же БД в потоковой среде?

Я пытаюсь определить поведение множественного подключения к базе данных в распределенной транзакции.

у меня есть длительный процесс, который порождает серию потоков, и каждый поток затем отвечает за управление его подключениями к БД и тому подобное. Все это выполняется внутри области транзакции, и каждый поток зачисляется в транзакцию через

1 ответов


во-первых, вы должны разделить то, что Вы читаете здесь и там о транзакциях 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));

и запустить код в моем примере. Вы увидите, что оба запроса выполняются параллельно, каждый из которых содержит 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();
        }

    }