-
Notifications
You must be signed in to change notification settings - Fork 285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Connection Pooling results in unpredictable isolation levels #1098
Comments
Note - when using this (rather crude) wrapper around the using System.Data;
namespace ConsoleApp1
{
public sealed class SafeConnection : IDbConnection
{
private readonly IDbConnection _connection;
public SafeConnection(IDbConnection connection)
{
_connection = connection;
}
public void Dispose()
{
_connection.Dispose();
}
public IDbTransaction BeginTransaction()
{
return _connection.BeginTransaction();
}
public IDbTransaction BeginTransaction(IsolationLevel il)
{
return _connection.BeginTransaction(il);
}
public void ChangeDatabase(string databaseName)
{
_connection.ChangeDatabase(databaseName);
}
public void Close()
{
_connection.Close();
}
public IDbCommand CreateCommand()
{
return _connection.CreateCommand();
}
public void Open()
{
_connection.Open();
var command = _connection.CreateCommand();
command.CommandText = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED;";
command.ExecuteNonQuery();
}
public string ConnectionString
{
get => _connection.ConnectionString;
set => _connection.ConnectionString = value;
}
public int ConnectionTimeout => _connection.ConnectionTimeout;
public string Database => _connection.Database;
public ConnectionState State => _connection.State;
}
} |
Similar to #96. |
Thank you for your quick response - I had not spotted that other ticket. Given it is 4 years old, am I right in assuming a fix is not likely to be on the horizon? I understand that the isolation level persisting for a connection after transaction rollback or commit is likely an intended behaviour within SQL Server's implementation, but when combined with connection pooling in the SqlClient it produces behaviour that seems both unexpected and potentially dangerous. Connection pooling is a useful abstraction to avoid having to manage actual connections manually, but I think this abstraction heavily implies that separate SqlConnections will be independent, regardless of whether they use the same underlying shared connection. After all, SqlConnection instances are opened, closed, and disposed, with the state of the actual connection being completely abstracted away behind these actions. Resetting the isolation level (as well as any other state on a connection) when a connection is returned to the pool (or alternatively when it is selected from the pool) would mean new SqlConnections are homogenous, and all behave identically, regardless of whether the connection has been used before and in what way. I have no idea how complicated implementing this would be, but I assume its non-trivial as the behaviour has been known about for several years. I would definitely add my +1 to any attempt to fix this though, as unexpected locking and dirty reads feels pretty dangerous. |
I will close this as duplicate, and you may track this topic with #96. |
Description
A connections default isolation level is "read commited". However, once a transaction has been committed or rolled back, the isolation level remains as it was set by the transaction, until the connection is closed or disposed. (see note under https://docs.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlconnection.begintransaction?view=sqlclient-dotnet-core-2.1)
However because SqlClient reuses connections in a pool, connections are not actually closed between uses of different
SqlConnection
instances. Because of this, after using aSqlConnection
and setting the isolation level via a transaction, anotherSqlConnection
created elsewhere can inherit this isolation level.This can cause unexpected increases in isolation level (especially when using Serializable and Repeatable Read) that can have knock on consequences such as deadlocks. In the other direction, you can get dirty reads if a connection inherits a "read uncommitted" isolation level from a previous use of the connection.
This strikes me as potentially very dangerous behaviour, as unless you explicitly set the isolation level for every use of the connection, you can get essentially a random level.
To reproduce
Code tested with Microsoft.Data.SqlClient 2.1.3 (and System.Data.SqlClient 4.4.0), and uses Dapper (2.0.90) to execute the SQL (though Dapper is not related to the problem).
Expected behavior
First batch of 10 tests show "read committed snapshot" isolation level
Second batch of 10 oscillate between "serializable" and "read uncommitted"
Third batch of 10 tests all show "read committed snapshot" again
Actual behavior
Third batch of 10 is a mismatch of "read committed snapshot", "read uncommitted" and "serializable"
Further technical details
Microsoft.Data.SqlClient version: 2.1.3
.NET target: Core 3.1
SQL Server version: SQL Server 2017
Operating system: Windows, Linux
The text was updated successfully, but these errors were encountered: