为什么要在 NET C# 中使用 TransactionScope 进行只读数据库调用?

Why would you use TransactionScope for Read-Only Database Calls in NET C#?

我继承了一个遗留系统,该系统存在严重的内存泄漏,这是由堆上分配的数百万个 InternalTransaction 对象引起的。我已将其追溯到数据库访问 class。在此 class 中,有一些方法可以从使用 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 调用没有指定 TransactionScope。

编辑:这个答案是错误的,但我把它留在这里是因为我从中学到了一些东西。 @stuartd 在评论中:

Err... you don't have to assign a using declaration to a variable. dotnetfiddle.net/U0PLBw

原来的错误答案: using 应该确保新的 TransactionScope 对象被释放,这应该确保它不会被泄露。但是,仔细观察,新的 TransactionScope 没有分配给任何局部变量,因此可能不适用于在 using 块末尾调用的 Dispose。

我建议您完全删除 using 是正确的(因为 TransactionScope 无论如何都不会被使用 - 它不可能);我还会向 Microsoft 报告这个问题,并建议这样的构造应该生成编译器警告,如果它还没有的话。

首先,在大多数关系数据库中,对数据库对象的读写操作总是在一个事务中执行。即使您没有显式启动事务,也会隐式启动一个事务。这应该至少回答了将事务用于读取语句是否有意义的问题——在大多数情况下,无论如何你最终都会得到一个事务。未隐式创建事务的示例是 SELECT 2 * 3 等不涉及数据库对象的查询。

当我们谈论事务时,我们也必须谈论隔离级别。 事务对读取语句很重要,就像它们对所有其他类型的数据库操作一样。隔离级别控制读取哪个一致性状态数据,以及是否放置读锁。在您的示例中,事务的隔离级别设置为 READ COMMITTED,这可能是故意设置的。假设您的数据库将其默认事务级别设置为 READ UNCOMMITTED - 此处使用错误的隔离级别可能会产生不良副作用,具体取决于您尝试通过该查询实现的目标。

我不会删除事务范围,只是因为它是一个读操作。您应该首先弄清楚您的数据库是如何配置的以及数据是如何写入您正在读取的table。

关于内存泄漏:您的范围显然已正确处理,所以我真的不明白为什么范围声明本身会导致内存泄漏。它也可能是您正在使用的数据库驱动程序版本中的错误。更有可能的是,该方法被调用得非常频繁,因此创建了大量与事务相关的对象。

我无法说出正确的方法是什么,因为我对您的应用程序以及该方法的调用方式知之甚少。不过,您可以考虑以下几点:

1) 使用Required代替RequiresNew。调用层次结构中的方法之一可能已经声明了事务范围。如果您使用 RequiresNew 创建嵌套事务范围,就像您的情况一样,它不会重用现有事务,而是创建一个新事务。将范围选项更改为 Required 将使其重用父范围中的事务,或者如果没有父范围,则创建一个新事务。

2) 您可以尝试将范围声明在调用层次结构中向上移动几层。 TransactionScope 是环境变量,因此后续方法调用中的事务范围是 "inherited"。这样您就可以潜在地减少创建的对象数量。