Лучше ли явно вызывать откат транзакции или позволить исключению вызвать неявный откат?

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

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

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

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

3 ответов


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

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

обновление

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


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

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

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

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


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

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

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

но, если API отличается, обработка фиксации также может быть (предоставлено, это :

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)
        }
    }
}

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