使用客户端框架、ORM 或类似构建查询(不支持 WITH(NOLOCK) 等查询提示)时,为单个事务实现不同隔离级别的最佳方法是什么?
想象一个应用程序使用 ReadUncommitted 级别进行许多复杂且长时间运行的查询(非常清楚相关风险),并且它应该使用 NHibernate 及其查询条件(或 QueryOver/LINQ,只是没有字符串连接!)。
NHibernate 不支持 with(nolock) 提示(使用本机 SQL 时除外,目前在许多情况下都使用该提示)。
因此,为了替换原生 SQL 字符串及其繁琐的构建代码,我想使用带有 IsolationLevel.ReadUncommitted 的事务来替换 'with(nolock)'。
但即使在 Commit/Rollback 之后,连接仍保持在更改后的隔离级别,以新级别运行所有内容。即使在 connection.Close() 之后,它也会返回到连接池并以更改的隔离级别重用。
我最初注意到这一点,因为我测试了打开一个具有快照隔离级别的连接并发送一个简单的查询,如果启用了数据库上的快照模式(通常不容易切换到快照),则禁用未提交的读取。测试数据库禁用了快照模式,所以我遇到了一个异常,并在 catch 块中将我的 UseReadUncommitted 变量设置为“true”,但后来来自“新”/重用连接的查询仍然得到相同的异常。
我编写了一个简单的类来将事务处理包装在 using 块中,自动重置 .Dispose() 中的 IsolationLevel。但这似乎会导致两次额外的数据库往返,并且我不确定更改后的隔离级别是否可能在某些情况下“幸存”处置并影响其他查询。该代码在第一次尝试中工作,它用于普通的 ADO.NET 连接/事务(如果好的话,我将为 NHibernate 会话做另一个!)。
有什么建议么?
public class TransactionContainerTempIsolationLevel : IDisposable
{
public IsolationLevel OldIsolationLevel { get; private set; }
public IsolationLevel TempIsolationLevel { get; private set; }
public IDbTransaction Transaction { get; private set; }
private readonly IDbConnection _conn;
public TransactionContainerTempIsolationLevel(IDbConnection connection, IsolationLevel tempIsolationLevel)
{
_conn = connection;
LocalIsolationLevel = localIsolationLevel;
var checkTran = _conn.BeginTransaction();
if (checkTran.IsolationLevel == tempIsolationLevel)
{
Transaction = checkTran;
}
else
{
OldIsolationLevel = checkTran.IsolationLevel;
checkTran.Dispose();
Transaction = _conn.BeginTransaction(tempIsolationLevel);
}
}
public void Dispose()
{
Transaction.Dispose();
if (OldIsolationLevel != TempIsolationLevel)
{
using (var restoreTran = _conn.BeginTransaction(OldIsolationLevel))
{
restoreTran.Commit();
}
}
}
}