Использование TransactionScope с MySQL и блокировка чтения - PullRequest
1 голос
/ 02 июня 2009

У меня следующая ситуация:

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

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

Exceute ("LOCK tbl1 READ");
Выполнить ("ВЫБРАТЬ ... из таблицы t1");
Выполнить («ВСТАВИТЬ в tbl2»);
Выполнить («РАЗБЛОКИРОВАТЬ СТОЛЫ»);

но поскольку я использую 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

Ответы [ 5 ]

2 голосов
/ 03 июня 2009

Если кому-то интересно, вот как 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, затем спит,
поток1 запускается, читает значение 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 - уровень изоляции Хаоса не поддерживается

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

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

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

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

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

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

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

1 голос
/ 02 июня 2009

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

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

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

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

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

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

1 голос
/ 02 июня 2009

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

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

0 голосов
/ 04 июня 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 потоков одновременно, и все работает как положено. Спасибо Рудольфсону за подсказку «ВЫБРАТЬ ОБНОВЛЕНИЕ».

0 голосов
/ 04 июня 2009

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

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

insert into tbl2 (id, ....) values (next_id(), ...)
...