正如之前的Stack Overflow问题(TransactionScope and Connection Pooling和How does SqlConnection manage IsolationLevel?)所证明的那样,事务隔离级别在与SQL Server和ADO.NET(也是System.Transactions和EF,因为它们构建)的池化连接中泄漏在ADO.NET之上。)
这意味着,在任何应用程序中都可能发生以下危险事件序列:
问题:防止这种情况的最佳方法是什么?现在到处都需要使用显式交易?
这是一个独立的复制品。您将看到第三个查询将继承第二个查询中的Serializable级别。
class Program
{
static void Main(string[] args)
{
RunTest(null);
RunTest(IsolationLevel.Serializable);
RunTest(null);
Console.ReadKey();
}
static void RunTest(IsolationLevel? isolationLevel)
{
using (var tran = isolationLevel == null ? null : new TransactionScope(0, new TransactionOptions() { IsolationLevel = isolationLevel.Value }))
using (var conn = new SqlConnection("Data Source=(local); Integrated Security=true; Initial Catalog=master;"))
{
conn.Open();
var cmd = new SqlCommand(@"
select
case transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncommitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'RepeatableRead'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot'
end as lvl, @@SPID
from sys.dm_exec_sessions
where session_id = @@SPID", conn);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine("Isolation Level = " + reader.GetValue(0) + ", SPID = " + reader.GetValue(1));
}
}
if (tran != null) tran.Complete();
}
}
}
输出:
Isolation Level = ReadCommitted, SPID = 51
Isolation Level = Serializable, SPID = 51
Isolation Level = Serializable, SPID = 51 //leaked!
答案 0 :(得分:28)
连接池在回收连接之前调用sp_resetconnection。重置事务隔离级别是sp_resetconnection所做的not in the list of things。这可以解释为什么“可序列化”泄漏到池化连接。
我猜您可以通过确保它位于right isolation level:
来启动每个查询if not exists (
select *
from sys.dm_exec_sessions
where session_id = @@SPID
and transaction_isolation_level = 2
)
set transaction isolation level read committed
另一个选项:与不同连接字符串的连接不共享连接池。因此,如果您对“可序列化”查询使用另一个连接字符串,则它们不会与“已提交读取”查询共享池。更改连接字符串的简单方法是使用其他登录名。您还可以添加Persist Security Info=False;
等随机选项。
最后,您可以确保每个“可序列化”查询在返回之前重置隔离级别。如果“序列化”查询无法完成,您可以clear the connection pool强制将受污染的连接从池中取出:
SqlConnection.ClearPool(yourSqlConnection);
这可能很昂贵,但查询失败很少,因此您不必经常致电ClearPool()
。
答案 1 :(得分:18)
在 SQL Server 2014 中,这似乎已得到修复。如果使用TDS protocol 7.3或更高版本。
在SQL Server 12.0.2000.8版上运行,输出为:
ReadCommitted
Serializable
ReadCommitted
不幸的是,在任何文档中都没有提到此更改,例如:
But the change has been documented on a Microsoft Forum.
不幸的是,这是后来的"未经修复的"在SQL Server 2014 CU6和SQL Server 2014 SP1 CU1中引入了一个错误:
"假设您在SQL Server客户端源代码中使用TransactionScope类,并且未在事务中显式打开SQL Server连接。释放SQL Server连接时,将错误地重置事务隔离级别。"
答案 2 :(得分:1)
对于在.NET中使用EF的用户,可以通过为每个隔离级别设置不同的应用程序名称(也由@Andomar声明)来为整个应用程序解决此问题:
//prevent isolationlevel leaks
//https://stackoverflow.com/questions/9851415/sql-server-isolation-level-leaks-across-pooled-connections
public static DataContext CreateContext()
{
string isolationlevel = Transaction.Current?.IsolationLevel.ToString();
string connectionString = ConfigurationManager.ConnectionStrings["yourconnection"].ConnectionString;
connectionString = Regex.Replace(connectionString, "APP=([^;]+)", "App=$1-" + isolationlevel, RegexOptions.IgnoreCase);
return new DataContext(connectionString);
}
奇怪的是,八年后这仍然是一个问题...
答案 3 :(得分:0)
我刚问了一个关于这个主题的问题,并添加了一段C#代码,它可以帮助解决这个问题(意思是:仅针对一个事务更改隔离级别)。
Change isolation level in individual ADO.NET transactions only
它基本上是一个包含在'使用' block,它在之前查询原始隔离级别并在以后恢复它。
但是,它确实需要两次额外的往返DB来检查和恢复默认的隔离级别,我并不完全确定它永远不会泄漏更改的隔离级别,尽管我看到的危险很小。