We have a problem with some database code that apparently executes with the wrong isolation level. In this particular part of the code, it is supposed to execute with "READ UNCOMMITTED" to minimize locks. Inconsistent data is OK at this point.
However, the code actually reads with READ COMMITTED, and we can't figure out why.
Here's what we did:
- Open the connection
- Execute on this connection "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"
- Hit a breakpoint
- Execute the SQL
On the breakpoint, we issue this command to the database:
select s.session_id, s.transaction_isolation_level, st.text from sys.dm_exec_sessions s
inner join sys.sysprocesses sp on (sp.spid = s.session_id)
CROSS APPLY sys.dm_exec_sql_text(sp.sql_handle) st
This SQL reports 4 pooled connections right now, one of which is our connection that we can step beyond the breakpoint to execute our SQL with, that has this state:
53 2 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
ie. session 53 has isolation level 2 (READ COMMITTED), and the last SQL that was executed on this session was that "SET TRANSACTION ..." command.
How can this be?
We verified with SQL Profiler that this connection did not live before our .NET code opened it, so it was not reused from the connection pool.
Yet, with a fresh connection, and the only and first SQL executed on it explicitly told it to use READ UNCOMMITTED, how can the connection still be READ COMMITTED?
What should we look at here?
The connection string (with bits redacted) is like this:
SERVER=hostname;DATABASE=dbname;Integrated Security=false;USER ID=sa;PASSWORD=****;Application Name=appname;Type System Version=SQL Server 2000;Workstation ID=hostname;
The connections are normal SqlConnection
connections, opened in the normal way.
Unfortunately we're unable to reproduce the problem if we write normal code opening a SqlConnection, so there has to be something with the application state, but since SqlProfiler and Sql Server both tells us that yes, the SQL was executed, but no, I don't care.
What can impact this?
The exact same code also opens other connections, that is, the code is executed many times and opens many connections, so more than one connection ends up in the pool, yet only the very first connection ends up having this problem.
This is SQL Server 2008 R2 and we have also reproduced this problem on 2012.
Edit
OK, some more information.
First, we are enabling pooling, or rather, we're not explicitly disabling it, nor are we twiddling the connection string to make "N" pools.
However, this connection is the first being opened with this particular connection string, thus it is not retrieved from the pool. Also see my note below about it being permanently "sick".
This connection is being set up like this:
var conn = new SqlConnection(...);
conn.StateChance += connection_StateChange;
private void connection_StateChange(Object sender, StateChangeEventArgs e)
{
if (e.CurrentState == ConnectionState.Open)
{
using (IDbCommand cmd = ((SqlConnection)sender).CreateCommand())
{
cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
cmd.ExecuteNonQuery();
}
We're not executing any other SQL before this.
Note that this code is used many times during the lifetime of the application, it is only the very first connection it opens that ends up being wrong.
This connection also becomes permanently sick. Since every time we open the connection (even though we might get it out of the connection pool), the above state change event executes, attempting to set the isolation level again. This also fails, but just for this single connection.
Additionally we've found one thing that impacts this since I posted this question.
By changing the connection string, that I posted above:
...;Type System Version=SQL Server 2000;...
to this:
...;Type System Version=SQL Server 2008;MultipleActiveResultSets=true;...
then this problem goes away, at the breakpoint listed earlier, the connection now has "READ UNCOMMITTED" state.
This was a red herring, the connection was no longer being reported in our overview until we had actually executed code there.
We're continuing our debugging.