我继承了一个旧系统,该系统由于堆上数百万个已分配的InternalTransaction对象而导致内存泄漏。我已经将其追溯到数据库访问类。在此类中,有一些方法使用数据库从库中read使用TransactionScope。
例如:
public IEnumerable<IEvent> GetFullEventHistory()
{
using (new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { Timeout = TimeSpan.Zero, IsolationLevel = IsolationLevel.ReadCommitted }))
{
var events = _store.GetFullEventHistory();
IList<IEvent> deserialisedEvents = GetEvents(events);
return deserialisedEvents;
}
}
我的直觉是删除包含TransactionScope的using()块。这是一个只读数据库调用,因此事务不执行任何操作。
这是正确的假设吗?还是我在这里缺少什么?
顺便说一下,未指定Write DB调用。
编辑:这个答案是错误的,但是我把它留在这里是因为我从中学到了一些东西。@stuartd发表评论:
Err ...您不必为变量分配using声明。dotnetfiddle.net/U0PLBw
原始错误答案:using
应确保释放新的TransactionScope对象,这应确保它不会泄漏。但是,仔细观察,新的TransactionScope不会分配给任何局部变量,因此在use块末尾调用Dispose的可能性不大。
我建议您完全删除使用是正确的(因为TransactionScope不会被使用-不会);我也将这一情况报告给Microsoft,并建议这样的构造(如果尚未生成的话应生成编译器警告)。
首先,在大多数关系数据库中,对数据库对象的读写操作将始终在事务内执行。即使您未明确启动事务,也将隐式启动事务。这至少应回答以下问题:在读语句中使用事务是否有意义–在大多数情况下,无论如何您最终都会获得事务。没有隐式创建事务的情况的一个例子是像SELECT 2 * 3
这样的查询,它不涉及数据库对象。
[当我们谈论事务时,我们还必须谈论隔离级别。 事务对于读取语句很重要,就像它们对所有其他类型的数据库操作一样。隔离级别控制在何处读取一致性状态数据以及是否放置读取锁。在您的示例中,事务的隔离级别设置为READ COMMITTED
,这可能是有意为之。想象一下,您的数据库的默认事务级别设置为READ UNCOMMITTED
-在这里使用错误的隔离级别可能会产生不良的副作用,具体取决于您要通过该查询尝试实现的目的。
我不会删除事务范围,只是因为它是读取操作。您应该首先弄清楚数据库的配置方式以及如何将数据写入正在读取的表中。
关于内存泄漏:您的范围显然已正确处理,因此我不明白为什么范围声明本身会导致内存泄漏。这也可能是您正在使用的数据库驱动程序版本中的错误。更可能的是,该方法经常被调用,从而创建许多与事务相关的对象。
我无法说出正确的方法,因为我对您的应用程序以及如何调用该方法不了解。不过,您可以考虑以下几点:
1)使用Required
代替RequiresNew
。调用层次结构中的一种方法可能已经声明了事务范围。如果像您的情况一样使用RequiresNew
创建嵌套事务作用域,它将不会重用该现有事务,而是会创建一个新的事务作用域。将范围选项更改为Required
将使其从父范围重新使用事务,如果没有父范围,则创建一个新的事务。
2)您可以尝试将范围声明在调用层次结构中上移几级。 TransactionScope
是环境变量,因此在后续方法调用中“继承”了事务作用域。这样,您就可以减少创建的对象的数量。