TransactionScope преждевременно завершен - PullRequest
64 голосов
/ 27 мая 2010

У меня есть блок кода, который выполняется в 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();

Ответы [ 7 ]

60 голосов
/ 08 июня 2010

Не забудьте подавить выбранные операторы из вашего 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 
  } 
} 
41 голосов
/ 04 апреля 2012

Я обнаружил, что это сообщение может появляться, когда транзакция выполняется дольше, чем 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

21 голосов
/ 17 февраля 2012

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

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.

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

11 голосов
/ 27 мая 2010

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

6 голосов
/ 11 марта 2013

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

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

    <system.transactions>
    <defaultSettings timeout="00:05:00" />
    </system.transactions>
    
  2. Но тогда это «ограничено» при настройке machine.config <machineSettings maxTimeout="00:10:00" />

6 голосов
/ 28 февраля 2012

Моя проблема была глупой, если вы будете сидеть в режиме отладки, вы получите это. Лицо Ладони

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

1 голос
/ 24 июня 2015

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

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

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

...