usr*_*usr 53 t-sql sql-server ado.net transactions transactionscope
正如先前的Stack Overflow问题(TransactionScope和连接池以及SqlConnection如何管理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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
Isolation Level = ReadCommitted, SPID = 51
Isolation Level = Serializable, SPID = 51
Isolation Level = Serializable, SPID = 51 //leaked!
Run Code Online (Sandbox Code Playgroud)
And*_*mar 30
连接池在回收连接之前调用sp_resetconnection.重置事务隔离级别不在 sp_resetconnection执行的事务列表中.这可以解释为什么"可序列化"泄漏到池化连接.
我想你可以通过确保它处于正确的隔离级别来启动每个查询:
if not exists (
select *
from sys.dm_exec_sessions
where session_id = @@SPID
and transaction_isolation_level = 2
)
set transaction isolation level read committed
Run Code Online (Sandbox Code Playgroud)
另一种选择:使用不同连接字符串的连接不共享连接池.因此,如果您对"可序列化"查询使用另一个连接字符串,则它们不会与"已提交读取"查询共享池.更改连接字符串的简单方法是使用其他登录名.你也可以添加一个随机选项Persist Security Info=False;.
最后,您可以确保每个"可序列化"查询在返回之前重置隔离级别.如果"serializable"查询无法完成,您可以清除连接池以强制将污染连接从池中取出:
SqlConnection.ClearPool(yourSqlConnection);
Run Code Online (Sandbox Code Playgroud)
这可能很昂贵,但查询失败很少,因此您不必ClearPool()经常打电话.
Tho*_*mas 19
在SQL Server 2014中,这似乎已得到修复.如果使用TDS协议7.3或更高版本.
在SQL Server 12.0.2000.8版上运行,输出为:
ReadCommitted
Serializable
ReadCommitted
Run Code Online (Sandbox Code Playgroud)
遗憾的是,此更改未在任何文档中提及,例如:
不幸的是,这在SQL Server 2014 CU6和SQL Server 2014 SP1 CU1中后来"不固定",因为它引入了一个错误:
FIX:在SQL Server 2014中发布SQL Server连接时,事务隔离级别被错误地重置
"假设您在SQL Server客户端源代码中使用TransactionScope类,并且未在事务中显式打开SQL Server连接.当释放SQL Server连接时,事务隔离级别将被错误地重置."
对于在 .NET 中使用 EF 的用户,您可以通过为每个隔离级别设置不同的应用程序名称来修复整个应用程序的此问题(也如 @Andomar 所述):
//prevent isolationlevel leaks
///sf/ask/689599081/
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);
}
Run Code Online (Sandbox Code Playgroud)
奇怪的是,8年后这仍然是一个问题......