Использование TransactionScope с MySQL и блокировкой чтения

У меня такая ситуация:

Если у вас есть база данных MySQL с таблицей InnoDB, которую я использую для хранения уникальных номеров. Я запускаю транзакцию, читаю значение (например, 1000471), сохраняю это значение в другой таблице и обновляю увеличенное значение (100472). Теперь я хочу избежать того, чтобы кто-то еще считал значение во время выполнения моей транзакции.

Если бы я использовал простой MySQL, я бы сделал что-то вроде этого:

Exceute ("LOCK tbl1 READ");
Execute ("SELECT ... from tbl1");
Execute ("INSERT into tbl2");
Execute ("UNLOCK TABLES");

но поскольку я использую SubSonic как DAL, а код не должен зависеть от mysql, я должен использовать TransactionScope.

Мой код:

        TransactionOptions TransOpt = new TransactionOptions();
        TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
        TransOpt.Timeout = new TimeSpan(0, 2, 0);

        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, TransOpt))
        {

             // Select Row from tbl1

             // Do something

             ts.Complete();
        }

Согласно TransactionOptions

system.transactions.isolationlevel

Эффект, которого я хочу достичь, может быть реализован с помощью IsolationLevel.ReadCommitted, но я все еще могу читать строку вне транзакции (если я попытаюсь ее изменить, я получаю блокировку, поэтому транзакция работает)

Есть ли у кого-нибудь предложения? Возможна ли блокировка чтения с помощью TransactionScope


person Jürgen Steinblock    schedule 02.06.2009    source источник
comment
Имейте в виду, что при использовании TransactionScope с удаленным сервером MySQL вам понадобится координатор распределенных транзакций как на локальном, так и на удаленном компьютере.   -  person Dario Solera    schedule 03.06.2009
comment
Это неправда, DTC требуется а) если это требуется реализации б) всегда, если вы используете более одной транзакции, например. using (TransactionScope ts = new TransactionScope ()) {- удалить файл (в Vista файловые операции поддерживают TransactionScope) - Выбрать * из таблицы на сервере 1 - Вставить в таблицу на сервере 2 - Удалить из таблицы на сервере 1}   -  person Jürgen Steinblock    schedule 03.06.2009


Ответы (5)


Если кому-то интересно, вот как TransactionOptions влияет на MySql:

Допустим, у меня есть два метода.

Method1 запускает транзакцию, выбирает строку из моей таблицы, увеличивает значение и обновляет таблицу.

Метод 2 такой же, но между выбором и обновлением я добавил сон на 1000 мс.

А теперь представьте, что у меня есть следующий код:

    Private Sub Button1_Click(sender as Object, e as System.EventArgs) Handles Button1.Click

        Dim thread1 As New Threading.Thread(AddressOf Method1)
        Dim thread2 As New Threading.Thread(AddressOf Method2)

        thread2.Start() // I start thread 2 first, because this one sleeps
        thread1.Start()

    End Sub

Без транзакций это могло бы произойти:
thread2 запускается, считывает значение 5, затем засыпает,
thread1 запускается, считывает значение 5, обновляет значение до 6,
thread2 также обновляет значение до 6.

Эффект: У меня два раза уникальный номер.

Что я хочу:
thread2 запускается, считывает значение 5, затем засыпает,
thread1 запускается, пытается прочитать значение, но получает блокировку и засыпает,
thread2 обновляет значение до 6,
thread1 продолжает, читает значение 6, обновляет значение до 7

Вот как начать транзакцию с помощью TransactionScope:

        TransactionOptions Opts = new TransactionOptions();
        Opts.IsolationLevel = IsolationLevel.ReadUncommitted;

        // start Transaction
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, Opts))
        {
            // Do your work and call complete
            ts.Complete();
        }

Это может даже управлять распределенными транзакциями. Если генерируется исключение, ts.Complete никогда не вызывается, а часть области действия Dispose () откатывает транзакцию.

Вот обзор того, как разные IsolationLevels влияют на транзакцию:

  • IsolationLevel.Chaos
    Выдает исключение NotSupportedException - уровень изоляции Chaos не поддерживается

  • IsolationLevel.ReadCommited
    Транзакции не мешают друг другу (два идентичных чтения, неверно)

  • IsolationLevel.ReadUncommitted
    Транзакции не мешают друг другу (два идентичных чтения, неверно)

  • IsolationLevel.RepeatableRead
    Транзакции не мешают друг другу (два идентичных чтения, неверно)

  • IsolationLevel.Serializable
    Выдает исключение MySqlException - обнаружена тупиковая ситуация при попытке получить блокировку; попробуйте перезапустить транзакцию во время обновления

  • IsolationLevel.Snapshot
    Выдает исключение MySqlException - у вас есть ошибка в синтаксисе SQL; проверьте руководство, соответствующее версии вашего сервера MySQL, на предмет правильного синтаксиса для использования рядом с "" в строке 1 во время Connection.Open ()

  • IsolationLevel.Unspecified
    Выдает исключение MySqlException - обнаружена тупиковая ситуация при попытке получить блокировку; попробуйте перезапустить транзакцию во время обновления

  • TransactionOptions not set
    Выдает исключение MySqlException - обнаружена тупиковая ситуация при попытке получить блокировку; попробуйте перезапустить транзакцию во время обновления

person Jürgen Steinblock    schedule 03.06.2009

Сначала я решил использовать SELECT FOR UPDATE, и после быстрого поиска я нашел страницу о блокировка чтения в справочнике MySQL 5.

Если я правильно понимаю, это не зависит от используемого уровня изоляции. И будьте осторожны - уровень изоляции просто говорит о том, как на текущую транзакцию влияют изменения в других транзакциях. Он не сообщает, что могут делать другие транзакции. Однако для более высоких уровней изоляции требуются более ограничивающие блокировки.

person rudolfson    schedule 02.06.2009
comment
Как я уже упоминал, я использую SubSonic для настойчивости. ‹Br› ‹br› Я мог бы запустить простой SQL для моей базы данных MySQL, но это нарушило бы концепцию дозвуковой обработки с несколькими базами данных. спасибо за ссылку, но я до сих пор не нашел решения, чтобы заблокировать строку для чтения с помощью mysql, это тоже стоит прочитать: dev.mysql.com/doc/refman/5.1/en/innodb-lock-modes.html Я обнаружил, если я не установил уровень IsolationLevel. При попытке получить блокировку обнаруживается тупик; попробуйте перезапустить транзакцию - MySqlException, что кажется хорошим (см. мой ответ) - person Jürgen Steinblock; 02.06.2009
comment
Я прочитал ссылку, которую вы дали, и, на мой взгляд, SELECT FOR UPDATE - это правильный способ явно заблокировать одну строку. Я не знаю SubSonic (предполагаю, что это инструмент ORM / структура постоянства) - вы проверяли его документацию, поддерживает ли она указание режима блокировки при чтении объектов? Я знаю эту возможность из других инструментов ORM, например. Гибернация (Java). Если я скажу Hibernate использовать, например, LockMode.UPGRADE, это приведет к ВЫБРАТЬ ДЛЯ ОБНОВЛЕНИЯ. - person rudolfson; 03.06.2009
comment
Ты прав. SELECT FOR UPDATE - правильный синтаксис в простом SQL. Проблема, похоже, заключается в том, что, используя подход TransactionScope, TransactionOption IsolationLevel может только контролировать, какие данные могут быть прочитаны (старые, незавершенные, новые) из текущей транзакции и что происходит с записью, если данные изменились (как вы упомянули в своем сообщении ), но не может вызвать блокировку UPDATE. Посмотри, что я нашел подлым: http://stackoverflow.com/questions/190666/linq-to-sql-and-concurrency-issues/190690 - person Jürgen Steinblock; 03.06.2009

Поскольку я не нашел способа заблокировать строку для чтения с помощью InnoDB и TransactionScope (я могу ошибаться), это должно сработать:

Если я запускаю две транзакции одновременно (без TransactionOptions) и одна завершается, другая не может быть завершена из-за исключения «взаимоблокировки».

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

если вы установите:

    TransactionOptions TransOpt = new TransactionOptions();
    TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;

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

person Jürgen Steinblock    schedule 02.06.2009

Поскольку SubSonic, похоже, не поддерживает SELECT ... FOR UPDATE, и, на мой взгляд, использование уровней изоляции было бы неправильным - как насчет наличия определяемая пользователем функция, которая возвращает новый идентификатор? Эта функция считывает текущее значение из tbl1 с SELECT ... FOR UPDATE, обновляет строку и возвращает значение.

В коде вашего приложения, где вы вставляете новое значение, которое вы бы просто использовали:

insert into tbl2 (id, ....) values (next_id(), ...)
person rudolfson    schedule 04.06.2009
comment
Было бы возможным решением, но в моем случае число - это не просто число, а строковое значение, которое я программно комбинирую со строкой маски для определения нового значения (например, текущий номер 200905123 и маска YYYYMM ### будут приведет к новому номеру 200906001, если он будет выполнен сегодня), и я не хочу вытаскивать эту логику из своего приложения. - person Jürgen Steinblock; 04.06.2009

Хорошо, я решил использовать «бэкдор» и теперь использую встроенный запрос:

        // start transaction
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            using (SharedDbConnectionScope scope = new SharedDbConnectionScope(DB._provider))
            {

                try
                {

                    Record r = new InlineQuery().ExecuteAsCollection<RecordCollection>(
                        String.Format("SELECT * FROM {0} WHERE {1} = ?param FOR UPDATE",
                                        Record.Schema.TableName,
                                        Record.Columns.Column1), "VALUE")[0];

                    // do something

                    r.Save();
                 }
             }
         }

Я запустил 10 потоков одновременно, и все работает как положено. Спасибо rudolfson за подсказку «ВЫБРАТЬ ДЛЯ ОБНОВЛЕНИЯ».

person Jürgen Steinblock    schedule 04.06.2009
comment
Я забыл упомянуть: для этого фрагмента кода требуется этот патч: code.google .com / p / subsonicproject / issues / detail? id = 96 (или измените его, чтобы не использовать параметр) - person Jürgen Steinblock; 04.06.2009