TransactionScope преждевременно завершен

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

Я считаю, что проблема возникает, когда срабатывает тупик, и функция пытается повторно отправить запрос. Это ошибка, которую я получаю:

Транзакция, связанная с текущим соединением, завершена, но не удалена. Транзакция должна быть удалена до того, как соединение будет использоваться для выполнения операторов SQL.

Это простой код, выполняющий запрос (весь код ниже выполняется при использовании TransactionScope):

using (sqlCommand.Connection = new SqlConnection(ConnectionStrings.App))
{
    sqlCommand.Connection.Open();
    sqlCommand.ExecuteNonQueryWithDeadlockHandling();
}

Вот метод расширения, который повторяет тупиковый запрос:

public static class SqlCommandExtender
{
    private const int DEADLOCK_ERROR = 1205;
    private const int MAXIMUM_DEADLOCK_RETRIES = 5;
    private const int SLEEP_INCREMENT = 100;

    public static void ExecuteNonQueryWithDeadlockHandling(this SqlCommand sqlCommand)
    {
        int count = 0;
        SqlException deadlockException = null;

        do
        {
            if (count > 0) Thread.Sleep(count * SLEEP_INCREMENT);
            deadlockException = ExecuteNonQuery(sqlCommand);
            count++;
        }
        while (deadlockException != null && count < MAXIMUM_DEADLOCK_RETRIES);

        if (deadlockException != null) throw deadlockException;
    }

    private static SqlException ExecuteNonQuery(SqlCommand sqlCommand)
    {
        try
        {
            sqlCommand.ExecuteNonQuery();
        }
        catch (SqlException exception)
        {
            if (exception.Number == DEADLOCK_ERROR) return exception;
            throw;
        }

        return null;
    }
}

Ошибка на линии:

sqlCommand.ExecuteNonQuery();

Ответ 1

Не забудьте подавить свои заявления выбора из вашего TransactionScope. В SQL Server 2005 и выше, даже когда вы используете (nolock), блокировки по-прежнему создаются в тех таблицах, которые выбирают. Проверьте это, он показывает вам как настроить и использовать TransactionScope.

using(TransactionScope ts = new TransactionScope 
{ 
  // db calls here are in the transaction 
  using(TransactionScope tsSuppressed = new TransactionScope (TransactionScopeOption.Suppress)) 
  { 
    // all db calls here are now not in the transaction 
  } 
} 

Ответ 2

Я обнаружил, что это сообщение может возникнуть, когда транзакция выполняется в течение более длительного периода, чем maxTimeout для System.Transactions. Не имеет значения, что TransactionOptions.Timeout увеличивается, он не может превышать maxTimeout.

Значение по умолчанию maxTimeout установлено в 10 минут, а его значение только может быть изменено в machine.config

Добавьте следующее (на уровне конфигурации) в machine.config, чтобы изменить таймаут:

<configuration>
    <system.transactions>
        <machineSettings maxTimeout="00:30:00" />
    </system.transactions>
</configuration>

Machine.config можно найти по адресу: %windir%\Microsoft.NET\Framework\[version]\config\machine.config

Подробнее об этом читайте в этом блоге: http://thecodesaysitall.blogspot.se/2012/04/long-running-systemtransactions.html

Ответ 3

Я могу воспроизвести проблему. Это таймаут транзакции.

using (new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 0, 0, 1)))
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var sqlCommand = connection.CreateCommand())
        {
            for (int i = 0; i < 10000; i++)
            {
                sqlCommand.CommandText = "select * from actor";
                using (var sqlDataReader = sqlCommand.ExecuteReader())
                {
                    while (sqlDataReader.Read())
                    {
                    }
                }
            }
        }
    }
}

Выдает System.InvalidOperationException с этим сообщением: "Сработала транзакция, связанная с текущим соединением но не был ликвидирован. Сделка должна быть удалена до соединение может использоваться для выполнения операторов SQL. "

Чтобы решить проблему, попробуйте выполнить запрос быстрее или выберите время ожидания.

Ответ 4

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

Ответ 5

Моя проблема была глупой, если вы сидите на отладочном перерыве через таймаут, вы получите это. Face Palm

Человек, программирование заставляет вас чувствовать себя толстыми в несколько дней...

Ответ 6

Подтвержденная ошибка также может быть вызвана таймаутом транзакции. Чтобы добавить к тому, что заявил Маркус + Рольф, если вы явно не задали тайм-аут в TransactionScope, тайм-аут TimeSpan будет принимать значение по умолчанию. Это значение по умолчанию - меньше:

  • Если вы переопределили локальный параметр app.config/web.config, например

    <system.transactions>
    <defaultSettings timeout="00:05:00" />
    </system.transactions>
    
  • Но это "capped" в настройке machine.config <machineSettings maxTimeout="00:10:00" />

Ответ 7

Это исключение также может быть вызвано отключить Microsoft Distributed Transaction Coordinator.

Если мы хотим включить его, мы запускаем " dcomcnfg" и выбираем "Component Services" -> "My Computer" -> "Distributed Transaction Coordinator" -> "Local Service DTC" и выбираем " Свойства".

Необходимо проверить " Разрешить удаленный клиент", " Разрешить входящие", " Разрешить исходящие" и " Нет аутентификации Обязательный".