views:

1841

answers:

10

I have a read query that I execute within a transaction so that I can specify the isolation level. Once the query is complete, what should I do?

  • Commit the transaction
  • Rollback the transaction
  • Do nothing (which will cause the transaction to be rolled back at the end of the using block)

What are the implications of doing each?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

EDIT: The question is not if a transaction should be used or if there are other ways to set the transaction level. The question is if it makes any difference that a transaction that does not modify anything is committed or rolled back. Is there a performance difference? Does it affect other connections? Any other differences?

+1  A: 

Given that a READ does not change state, I would do nothing. Performing a commit will do nothing, except waste a cycle to send the request to the database. You haven't performed an operation that has changed state. Likewise for the rollback.

You should however, be sure to clean up your objects and close your connections to the database. Not closing your connections can lead to issues if this code gets called repeatedly.

Brett McCann
Depending on the isolation level, a select CAN obtain locks that will block other transactions.
Graeme Perrow
The connection will be closed at the end of the using block- that's what it's there for. But good point that the network traffic is probably the slowest part of the equation.
Joel Coehoorn
The transaction will be committed or rolled back one way or another, so best practice would be to always issue a commit if it succeeded.
Neil Barnwell
A: 

The first question that comes to mind is: why would you make a read-only query transactional? I would not incur the overhead of starting a new transaction unless you are modifying data.

If you have a series of queries all running in a transactional context, some read-only and some updates, then by all means use your normal commit/rollback patterns.

Dave Swersky
The transaction is started so that I can specify the isolation level of the read query.
Stefan Moser
In other words: you can use the transaction to _reduce_ the amount of locking done by the server.
Joel Coehoorn
Or you can use the transaction to ensure the consistency of the data you see - by setting the isolation correctly.
Jonathan Leffler
A: 

Do you need to block others from reading the same data? Why use a transaction?

@Joel - My question would be better phrased as "Why use a transaction on a read query?"

@Stefan - If you are going to use AdHoc SQL and not a stored proc, then just add the WITH (NOLOCK) after the tables in the query. This way you dont incur the overhead (albeit minimal) in the application and the database for a transaction.

SELECT * FROM SomeTable WITH (NOLOCK)

EDIT @ Comment 3: Since you had "sqlserver" in the question tags, I had assumed MSSQLServer was the target product. Now that that point has been clarified, I have edited the tags to remove the specific product reference.

I am still not sure of why you want to make a transaction on a read op in the first place.

StingyJack
To the set isolation level all at once. You can use the transaction to actually _reduce_ the amount of locking for the query.
Joel Coehoorn
I'm using the transaction so that I can use a lower isolation level and reduce locking.
Stefan Moser
@StingyJack - This code can execute against a number of different databases, so NOLOCK is not an option.
Stefan Moser
+1  A: 

Just a side note, but you can also write that code like this:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

And if you re-structure things just a little bit you might be able to move the using block for the IDataReader up to the top as well.

Joel Coehoorn
+2  A: 

If you begin a transaction, then best practice is always to commit it. If an exception is thrown inside your use(transaction) block the transaction will be automatically rolled-back.

Neil Barnwell
A: 

In your code sample, where you have

  1. // Do something useful

    Are you executing a SQL Statement that changes data ?

If not, there's no such thing as a "Read" Transaction... Only changes from an Insert, Update and Delete Statements (statements that can change data) are in a Transaction... What you are talking about is the locks that SQL Server puts on the data you are reading, because of OTHER transactions that affect that data. The level of these locks is dependant on the SQL Server Isolation Level.

But you cannot Commit, or ROll Back anything, if your SQL statement has not changed anything.

If you are changing data, then you can change the isolation level without explicitly starting a transation... Every individual SQL Statement is implicitly in a transaction. explicitly starting a Transaction is only necessary to ensure that 2 or more statements are within the same transaction.

If all you want to do is set the transaction isolation level, then just set a command's CommandText to "Set Transaction Isolation level Repeatable Read" (or whatever level you want), set the CommandType to CommandType.Text, and execute the command. (you can use Command.ExecuteNonQuery() )

NOTE: If you are doing MULTIPLE read statements, and want them all to "see" the same state of the database as the first one, then you need to set the isolation Level top Repeatable Read or Serializable...

Charles Bretana
// Do something useful does not change any data, just read. All I want to do is specify the isolation level of the query.
Stefan Moser
Then you can do that without explicitly starting a transaction from the client... Just execute the sql string "Set Transaction Isolation Level ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot", or "... Serializable" "Set Isolation Level Read Committed"
Charles Bretana
Transactions still matter even if you're only reading. If you want to do several read operations, doing them inside a transaction will ensure consistency. Doing them without one won't.
MarkR
yes sorry, you are right, at least this is true if the if the Isolation level is set to Repeatable Read or higher.
Charles Bretana
+6  A: 

If you haven't changed anything, then you can use either a COMMIT or a ROLLBACK. Either one will release any read locks you have acquired and since you haven't made any other changes, they will be equivalent.

Graeme Perrow
+1  A: 

If you put the SQL into a stored procedure and add this above the query:

set transaction isolation level read uncommitted

then you don't have to jump through any hoops in the C# code. Setting the transaction isolation level in a stored procedure does not cause the setting to apply to all future uses of that connection (which is something you have to worry about with other settings since the connections are pooled). At the end of the stored procedure it just goes back to whatever the connection was initialized with.

Eric Z Beard
+13  A: 

You commit. Period. There's no other sensible alternative. If you started a transaction, you should close it. Committing releases any locks you may have had, and is equally sensible with ReadUncommitted or Serializable isolation levels. Relying on implicit rollback - while perhaps technically equivalent - is just poor form.

If that hasn't convinced you, just imagine the next guy who inserts an update statement in the middle of your code, and has to track down the implicit rollback that occurs and removes his data.

Mark Brackett
There is a sensible alternative - rollback. Explicit rollback, that is. If you didn't mean to change anything, rollback ensures anything is undone. Of course, there shouldn't have been any changes; rollback guarantees that.
Jonathan Leffler
Different DBMS can have different 'implicit transaction completion' semantics. IBM Informix (and I believe DB2) do implicit rollback; by rumour, Oracle does an implicit commit. I prefer implicit rollback.
Jonathan Leffler
Suppose I create a temp table, populate it with ids, join it with a data table to select the data that goes with the ids, then delete the temp table. I'm really just reading data, and I don't care what happens to the temp table, since it's temporary... but from a performance perspective, would it be more expensive to rollback the transaction or commit it? What's the effect of a commit/rollback when nothing but temp tables and read operations are involved?
Triynko
@Triynko - Intuitively, I'd guess that ROLLBACK is more expensive. COMMIT is the normal use case, and ROLLBACK the exceptional case. But, except academically, who cares? I'm sure there's 1000 better optimization points for your app. If you're really curious, you can find the mySQL transaction handling code at http://bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/head:/sql/transaction.cc
Mark Brackett
@Jonathan - "Of course, there shouldn't have been any changes; rollback guarantees that." If you're not confident enough in your SQL statements to know whether they changed anything or not, I'd suggest that you have much bigger problems. Unfortunately, the next guy who *wants* to change data (and has written the SQL command to do so) has to track down your unintuitive "rollback the work that I didn't do" statement and remove it.I'd allow an exception for things like integration tests and such, since they are explictly about *not* changing data - even if you have an update statement.
Mark Brackett
@Mark. It's an optimization point, therefore I optimize it. It's called frequently enough (as a matter of user scale, not app design) that it needs to be as fast as possible. Intuitively one would think rollback is more expensive... but that depends on how the database commits a transaction vs rolls it back, which is why I'm asking. A rollback could simply throw out work done in-memory for example, while a commit may alter database stats or something.
Triynko
@Triynko - The *only* way to optimize is to profile. It's such a simple code change, there's no reason not to profile both methods if you really want to optimize it. Make sure to update us with results!
Mark Brackett
A: 

IMHO it can make sense to wrap read only queries in transactions as (especially in Java) you can tell the transaction to be "read-only" which in turn the JDBC driver can consider optimizing the query (but does not have to, so nobody will prevent you from issuing an INSERT nevertheless). E.g. the Oracle driver will completely avoid table locks on queries in a transaction marked read-only, which gains a lot of performance on heavily read-driven applications.

Oliver Gierke