Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it a better practice to explicitly call transaction rollback or let an exception trigger an implicit rollback?

In the below code if any exception is thrown while executing the the SQL statements we should expect an implicit rollback on the transaction as the transaction was not committed, it goes out of scope and it gets disposed:

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

Is the above an acceptable practice, or should one catch the exception and explicitly make a call to tran.Rollback() as shown below:

using (DbTransaction tran = conn.BeginTransaction())
{
    try
    {
        //
        // Execute SQL statements here...
        //
        tran.Commit();
    }
    catch
    {
        tran.Rollback();
        throw;
    }
}
like image 266
Elan Avatar asked Jun 21 '11 00:06

Elan


People also ask

Is transaction should end with either commit or ROLLBACK?

A transaction ends when it is committed or rolled back, either explicitly (with a COMMIT or ROLLBACK statement) or implicitly (when a DDL statement is issued). To illustrate the concept of a transaction, consider a banking database.

Can we execute commit immediately after ROLLBACK?

I have confirmed that after rollback we cannot commit the same transaction. Make sure another transaction is not in waiting, else it will be committed.

What happens if we ROLLBACK after commit?

After you commit the transaction, the changes are visible to other users' statements that execute after the commit. You can roll back (undo) any changes made during the transaction with the ROLLBACK statement (see ROLLBACK.

Can we ROLLBACK committed transaction in SQL?

Once SQL Server commits a transaction, you cannot run the ROLLBACK statement.


3 Answers

Former. If you look up MSDN samples on similar topics, like TransactionScope, they all favor the implicit rollback. There are various reasons for that, but I'll just give you a very simple one: by the time you catch the exception, the transaction may had already rolled back. Many errors rollback the pending transaction and then they return control to the client, where the ADO.Net raises the CLR SqlException after the transaction was already rolled back on the server (1205 DEADLOCK is the typical example of such an error), so the explicit Rollback() call is, at best, a no-op, and at worse an error. The provider of the DbTransaction (eg. SqlTransaction) should know how to handle this case, eg. because there is explicit chat between the server and the client notifying of the fact that the transaction rolled back already, and the Dispose() method does the right thing.

A second reason is that transactions can be nested, but the semantics of ROLLBACK are that one rollback rolls back all transactions, so you only need to call it once (unlike Commit() which commits only the inner most transaction and has to be called paired up for each begin). Again, Dispose() does the right thing.

Update

The MSDN sample for SqlConnection.BeginTransaction() actually favors the second form and does an explicit Rollback() in the catch block. I suspect the technical writer simply intended to show in one single sample both Rollback() and Commit(), notice how he needed to add a second try/catch block around the Rollback to circumvent exactly some of the problems I mentioned originally.

like image 156
Remus Rusanu Avatar answered Oct 11 '22 02:10

Remus Rusanu


You can go either way, the former being more concise, the latter being more intent revealing.

A caveat with the first approach would be that calling RollBack on disposal of transaction is dependent on the driver specific implementation. Hopefully almost all the .NET connectors do that. SqlTransaction does:

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's:

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

A caveat with second approach is it's not safe to call RollBack without another try-catch. This is explicitly stated in the documentation.

In short as to which is better: it depends on the driver, but it's typically better to go for the first, for the reasons mentioned by Remus.

Additionally see What happens to an uncommitted transaction when the connection is closed? for as to how connection disposal treat commits and rollbacks.

like image 3
nawfal Avatar answered Oct 11 '22 04:10

nawfal


I tend to agree with "Implicit" rollback based on exception pathways. But, obviously, that depends on where you are in the stack and what you're trying to get done (i.e. is the DBTranscation class catching the exception and performing cleanup, or is it passively not "committing"?).

Here's a case where implicit handling makes sense (maybe):

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

But, if the API is different, the handling of commit might be, too (granted, this :

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

I can't think of too many cases where explicitly rolling-back is the right approach except where there is a strict change control policy to be enforced. But then again, I'm a bit slow on the uptake.

like image 2
Lee Avatar answered Oct 11 '22 02:10

Lee