Лучше ли явно вызывать откат транзакции или позволить исключению вызвать неявный откат? - PullRequest
19 голосов
/ 21 июня 2011

В приведенном ниже коде, если при выполнении операторов SQL выдается какое-либо исключение, следует ожидать неявного отката транзакции, поскольку транзакция не была зафиксирована, она выходит из области видимости и удаляется:

using (DbTransaction tran = conn.BeginTransaction())
{
    //
    // Execute SQL statements here...
    //
    tran.Commit();
}

Является ли вышеуказанное приемлемой практикой, или следует перехватить исключение и явно сделать вызов функции tran.Rollback (), как показано ниже:

using (DbTransaction tran = conn.BeginTransaction())
{
    try
    {
        //
        // Execute SQL statements here...
        //
        tran.Commit();
    }
    catch
    {
        tran.Rollback();
        throw;
    }
}

Ответы [ 3 ]

20 голосов
/ 21 июня 2011

Прежнее. Если вы посмотрите примеры MSND по схожим темам, таким как TransactionScope, все они предпочитают неявный откат. Для этого есть различные причины, но я просто приведу очень простую: к тому времени, когда вы поймаете исключение, транзакция может иметь уже откат. Многие ошибки откатывают ожидающую транзакцию и , затем возвращают управление клиенту, когда ADO.Net вызывает CLR SqlException после транзакция уже откатилась на сервере (1205 DEADLOCK это типичный пример такой ошибки), поэтому явный вызов Rollback() в лучшем случае не работает, а в худшем - ошибка. Поставщик DbTransaction (например, SqlTransaction) должен знать, как обрабатывать этот случай, например. потому что между сервером и клиентом существует явный чат, уведомляющий о том, что транзакция уже откатана, а метод Dispose() делает правильные вещи.

Вторая причина заключается в том, что транзакции могут быть вложенными, но семантика ROLLBACK заключается в том, что один откат откатывает всех транзакций, поэтому вам нужно вызывать его только один раз (в отличие от Commit(), который фиксирует только самая внутренняя транзакция и должна вызываться в паре для каждого начала). Опять же, Dispose() делает правильные вещи.

Обновление

Образец MSDN для SqlConnection.BeginTransaction() фактически поддерживает вторую форму и выполняет явный Rollback() в блоке catch. Я подозреваю, что технический писатель просто намеревался показать в одном примере и Rollback(), и Commit(), заметьте, как ему нужно было добавить второй блок try / catch вокруг Rollback, чтобы обойти точно некоторые из проблем, которые я упоминал изначально.

3 голосов
/ 01 февраля 2013

Вы можете пойти в любом направлении, первый из которых более лаконичен, а второй более откровенен.

Предостережение при первом подходе заключается в том, что вызов RollBack при удалении транзакции зависит от конкретной реализации драйвера. Надеюсь, что почти все .NET-коннекторы это делают. SqlTransaction делает:

private void Dispose(bool disposing)
{
    Bid.PoolerTrace("<sc.SqlInteralTransaction.Dispose|RES|CPOOL> %d#, Disposing\n", this.ObjectID);
    if (disposing && (this._innerConnection != null))
    {
        this._disposing = true;
        this.Rollback();
    }
}

MySQL:

protected override void Dispose(bool disposing)
{
  if ((conn != null && conn.State == ConnectionState.Open || conn.SoftClosed) && open)
    Rollback();
  base.Dispose(disposing);
}

Предостережение со вторым подходом - небезопасно звонить RollBack без другого try-catch. Это прямо указано в документации .

Короче говоря, что лучше: это зависит от драйвера, но обычно лучше идти первым, по причинам, указанным Ремусом.

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

1 голос
/ 21 июня 2011

Я склонен согласиться с «неявным» откатом на основе путей исключения.Но, очевидно, это зависит от того, где вы находитесь в стеке и что вы пытаетесь выполнить (то есть класс DBTranscation перехватывает исключение и выполняет очистку, или он пассивно не "фиксирует"?).

Вот случай, когда неявная обработка имеет смысл (возможно):

static T WithTranaction<T>(this SqlConnection con, Func<T> do) {
    using (var txn = con.BeginTransaction()) {
        return do();
    }
}

Но, если API другой, обработка commit тоже может быть (предоставлена, это:

static T WithTranaction<T>(this SqlConnection con, Func<T> do, 
    Action<SqlTransaction> success = null, Action<SqlTransaction> failure = null) 
{
    using (var txn = con.BeginTransaction()) {
        try {
            T t = do();
            success(txn); // does it matter if the callback commits?
            return t;
        } catch (Exception e) {
            failure(txn); // does it matter if the callback rolls-back or commits?
            // throw new Exception("Doh!", e); // kills the transaction for certain
            // return default(T); // if not throwing, we need to do something (bogus)
        }
    }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...