Повторная блокировка в транзакции - PullRequest
3 голосов
/ 13 мая 2011

У меня есть служба окна C #, которая общается с несколькими базами данных на сервере MS SQL.Он многопоточный и имеет много функций, каждая из которых содержит длинный список операций с базой данных, каждая из которых выполняется в рамках своей собственной транзакции.Таким образом, типичная функция выглядит как

    public void DoSomeDBWork()
    {
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            DatabaseUpdate1();
            DatabaseUpdate2();
            DatabaseUpdate3();
            DatabaseUpdate4();
            DatabaseUpdate5();

            DatabaseUpdate6();

        }
    }

При большой нагрузке мы сталкиваемся с тупиками.У меня вопрос: если я напишу какой-нибудь код C # для автоматической повторной отправки DatabaseUpdate в случае тупика, он будет удерживать ресурсы для незавершенных операций?например, если в DatabaseUpdate6 () возникает исключение взаимоблокировки, и я повторяю его 3 раза с ожиданием 3 секунды, в течение этого времени все незавершенные операции «DatabaseUpdates 1–5» будут удерживаться на их ресурсах, что может еще больше увеличить вероятностьбольше тупиков?Это даже хорошая практика, чтобы повторить попытку в случае тупиков.

Ответы [ 4 ]

11 голосов
/ 13 мая 2011

Вы лаете не на то дерево.

Deadlock означает, что вся область транзакции отменена. В зависимости от вашего приложения, вы можете перезапустить с блока using, т.е. новый TransactionScope, но это вряд ли будет правильным. Причина, по которой вы видите тупик, заключается в том, что кто-то другой изменил данные, которые вы тоже меняли. Поскольку большинство из этих обновлений применяют обновление к значению, ранее считанному из базы данных, тупик является четким признаком того, что все прочитанное вами было изменено . Поэтому повторное применение ваших обновлений без чтения приведет к перезаписи всего, что было изменено другой транзакцией, что приведет к потере обновлений. Вот почему тупиковая блокировка почти никогда не может быть «автоматически» повторена, новые данные должны быть повторно загружены из БД, если было задействовано действие пользователя (например, редактирование формы), то пользователь должен быть уведомлен и должен повторно подтвердить изменения, и только тогда обновление можно попробовать снова. Только определенные типы действий автоматической обработки могут быть отменены, но они никогда не повторяются, как в «попытаться написать снова», но они всегда действуют в цикле «чтение-обновление-запись», и взаимоблокировки заставят цикл повторить попытку, и так как они всегда начинаются с «читать». Они автоматически самокорректируются.

При этом ваш код блокируется, скорее всего, из-за злоупотребления уровнем изоляции сериализации, когда в этом нет необходимости: с использованием нового TransactionScope () считается вредным . Вы должны перезаписать параметры транзакции, чтобы использовать уровень изоляции ReadCommitted, сериализуемый почти никогда не требуется и является гарантированным способом достижения взаимоблокировок.

Второй вопрос - Почему блокирует сериализацию? Он блокируется из-за сканирования таблиц, которые указывают, что у вас нет надлежащих индексов для чтения и обновлений.

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

6 голосов
/ 25 мая 2011

Это касается не всего в вашем вопросе, но по вопросу повторных попыток. Идея повторных попыток транзакции, базы данных или нет, опасна, и вы не должны читать это, если слово «идемпотент» ничего не значит для вас (честно говоря, я тоже не знаю достаточно об этом, но мое руководство было последнее слово, и я пошел писать в повторных попытках тупиковых ситуаций. Я поговорил с парочкой самых умных парней, которых я знаю в этой области, и все они вернулись ко мне с «ПЛОХОЙ ПЛОХОЙ», так что я не чувствую себя хорошо в связи с фиксацией этого источника. чтобы сделать это так, это может также сделать это забавным ..., вот что я написал недавно, чтобы повторить MySql взаимоблокировку определенное количество раз перед броском и возвратом

При использовании анонимного метода у вас должен быть только один получатель, который может динамически обрабатывать сигнатуры методов и универсальные типы возвращаемых данных. Вам также понадобится аналогичный для void return, который будет просто использовать Action (). Для MSSQL, я думаю, он будет выглядеть почти идентично, за исключением 'my'

.
  1. Обработчик, который выполняет повторную попытку:

    //

    private T AttemptActionReturnObject<T>(Func<T> action)
            {
                var attemptCount = 0;
    
                do
                {
                    attemptCount++;
                    try
                    {
                        return action();
                    }
                    catch (MySqlException ex)
                    {
                        if (attemptCount <= DB_DEADLOCK_RETRY_COUNT)
                        {
                            switch (ex.Number)
                            {
                                case 1205: //(ER_LOCK_WAIT_TIMEOUT) Lock wait timeout exceeded
                                case 1213: //(ER_LOCK_DEADLOCK) Deadlock found when trying to get lock
                                    Thread.Sleep(attemptCount*1000);
                                    break;
                                default:
                                    throw;
                            }
                        }
                        else
                        {
                            throw;
                        }
                    }
                } while (true);
            }
    
  2. Оберните ваш вызов метода с делегатом или лямбда

        public int ExecuteNonQuery(MySqlConnection connection, string commandText, params MySqlParameter[] commandParameters)
    {
        try
        {
            return AttemptActionReturnObject( () => MySqlHelper.ExecuteNonQuery(connection, commandText, commandParameters) );
        }
        catch (Exception ex)
        {
            throw new Exception(ex.ToString() + " For SQL Statement:" + commandText);
        }
    }
    

это также может выглядеть так:

return AttemptActionReturnObject(delegate { return MySqlHelper.ExecuteNonQuery(connection, commandText, commandParameters); });
1 голос
/ 14 мая 2011

Когда SQL обнаруживает взаимоблокировку, он убивает один поток и сообщает об ошибке.Если ваш поток уничтожен, он автоматически откатывает все незафиксированные транзакции - в вашем случае ВСЕ из DatabaseUpdate*(), которые уже были выполнены во время этой последней транзакции.

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

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

1 голос
/ 13 мая 2011

Я думаю, что разные серверы баз данных могут по-разному реагировать на взаимоблокировку, однако с SQL Server, если две транзакции заблокированы, одна из них выбирается сервером в качестве жертвы взаимоблокировки (ошибка 1205), и эта транзакция откатывается.Конечно, это означает, что другая транзакция может быть выполнена.

Если вы являетесь жертвой тупика, вам придется повторить все обновления базы данных, а не только update6.

В ответ накомментарии об избежании взаимоблокировок с подсказками, такими как NOLOCK, я настоятельно рекомендую против этого.

Дедлоки - это просто факт жизни.Представьте себе, два пользователя, каждый из которых подает ручную запись в журнал в систему бухгалтерского учета. Первая запись делает кредит банковского счета и дебет дебиторской задолженности.Вторая запись дебетует банк ar & credit.

Теперь представьте, что обе транзакции воспроизводятся одновременно (что редко случается при тестировании)

транзакция 1 блокирует банковский счеттранзакция 2 блокирует учетную запись a / r.
транзакция 1 пытается заблокировать дебиторскую задолженность и блокирует ожидание транзакции 2. транзакция 2 пытается заблокировать банк, и тупик автоматически и мгновенно обнаруживается.одна из транзакций считается жертвой тупика и откатывается.Другая транзакция происходит так, как будто ничего не произошло.

Блокировки - это реальность, и способ реагирования на них довольно прост.«Пожалуйста, повесьте трубку и повторите попытку.»

См. MSDN для получения дополнительной информации об обработке взаимоблокировок с помощью SQL Server

...