林克 System.OutofMemoryException

Linq System.OutofMemoryException

我在 C# 中有一个很长的 运行 进程,它会在任何地方点击 Sql table 10 到 200 次。当进程每次从同一个 table 中超过大约 50 次点击和查询超过大约 100,000 行时,它将在此行抛出系统内存不足异常,特别是在将 IQuery 对象转换为名单:

var cht = from p in _db.TickerData
          where p.Time >= Convert.ToDateTime(start) &&
          p.Time <= Convert.ToDateTime(end)
          orderby p.Time
          select p;

_prices = cht.ToList();    < this is where the System.OutofMemoryException occurs >

我该怎么做才能避免这个错误?

首发:

specifically at the bottom where it converts the IQuery object to a List

是的,这就是您预计会发生内存不足情况的地方。

上面cht的分配实际上并没有命中数据库;它所做的只是声明查询的形状。这称为 延迟执行 并且 LINQ 到处都在使用它。意思是"we don't actually process anything until your code needs it."

调用 ToList,不过,本质上是说 "the code needs it, all of it, right now." 所以这就是它将查询发送到数据库的地方,一次拉回所有结果,使用 LINQ 魔法将它们转换为 CLR 对象,然后将它们全部塞进一个 List<T> 中。

话虽如此,这只是一种预感,但您的 LINQ 提供程序可能不知道 Convert.ToDateTime 是什么。如果它不知道如何处理它,它不会将它放入它执行的查询中的 WHERE 子句中,而是加载整个 table 并在客户端过滤它,这可能就是为什么当 table 变得太大时,而不是当 结果集 变得太大时,你会崩溃。

要验证这一点,请使用数据库分析器拦截查询,并查看 WHERE 子句是否符合您的预期。如果翻译不正确,试试这个:

var startTime = Convert.ToDateTime(start);
var endTime = Convert.ToDateTime(end);
var cht = from p in _db.TickerData
          where p.Time >= startTime && p.Time <= endTime
          orderby p.Time
          select p;
_prices = cht.ToList();

如果这没有帮助,您可能只是撤回了太多数据,并且您将不得不以与在任何其他上下文中处理过多数据相同的方式进行处理。

您尝试检索的数据对于您的列表来说太大了。异常出现在 ToList() 上,因为它正是执行查询的地方。 你想用这么大的列表实现什么?可能的解决方案是:

1) 使用更多条件限制您的搜索。不要加载全部数据,而是加载其中的一部分,如果您确实需要它,还可以加载另一部分。

2) 如果要将整个数据加载到内存中,请使用不同于列表的其他数据结构,查看 ConcurrentDictionary

您的问题是查询 returns 需要存储在我们进程内存中的大量数据。非常多的数据 => OutOfMemoryException。这很正常。不正常的是试图做这样的事情。相反,您可以通过一些额外的过滤来限制结果集,或者将大的结果集分成较小的结果集,可能像这样:

        DateTime startDateTime = Convert.ToDateTime(start);
        DateTime endDateTime = Convert.ToDateTime(end);
        int fetched = 0;
        int totalFetched = 0;

        do
        {
            //fetch a batch of 1000 records
            var _prices = _db.TickerData.Where(p => p.Time >= startDateTime && p.Time <= endDateTime)
                                    .OrderBy(p => p.Time)
                                    .Skip(totalFetched)
                                    .Take(1000)
                                    .ToList();                

            //do here whatever you want with your batch of 1000 records
            fetched = _prices.Count;
            totalFetched += fetched;
        }
        while (fetched > 0);

通过这种方式,您可以批量处理任意数量的数据。

编辑:修复了评论部分@Code.me报告的一些问题。

编辑:我建议您在数据库级别的时间列上设置一个索引(如果您还没有的话)以加快这些查询。

由于延迟执行,查询将在您对其调用 ToList() 时执行。由于加载所有数据会占用太多内存,因此最好进行批处理。

下面的代码可以让您一次获取 1000 条(或最适合您的)记录,然后您可以处理它们。

var startTime = Convert.ToDateTime(start);
var endTime = Convert.ToDateTime(end);

IEnumerable<T> prices= new List<T>();  // whatever T is

var currentFetched = 0;
var totalFetched = 0;

do
{
    var cht = _db.TickerData.Where(p => p.Time >= startTime && p.Time << endTime)
                        .OrderBy(p => p.Time)
                        .Skip(totalFetched)
                        .Take(1000)
                        .ToList();

    currentFetched = cht.Count();
    totalFetched += currentFetched;

    // prices = prices.Concat(cht).ToList();  
    // ^ This might throw an exception later when the list becomes too big 
    // So you can probably process currently fetched data
}
while (currentFetched > 0);

我对我所做的只是让它 return 成为一个 IQueryable 对象。它仍然是一个列表,但它的性能要好得多。