【问题标题】:LINQ to Entities - Skip and Take very slowLINQ to Entities - 跳过并采取非常慢
【发布时间】:2018-03-30 09:52:58
【问题描述】:

我正在编写一个处理大量图像的替换系统。我已将被视为“实时”图像的内容迁移到新数据库,但需要通过 UI 搜索旧数据库以允许用户拉取不同的图像。

我在我的数据库中放置了一个查询旧数据库的视图,以便我可以通过实体框架公开它。该视图运行良好,尽管有数百万条记录,但我很快就获得了结果。

我的问题是当我尝试使用 LINQ to Entities 运行查询时。我的搜索是分页的,并将结果限制为每页 100 个。

我有一个用于过滤结果的 IQueryable,但对于 Skip/Take,我必须添加一个 order by。下面的示例显示了获取结果的第 2 页(跳过 100,获取 100)。

        if (firstId.HasValue)
            query = query.Where(x => x.Id >= firstId.Value);

        if (!string.IsNullOrEmpty(groupCode))
            query = query.Where(x => x.GroupCode == groupCode);

        var daResults = query
            .OrderBy(x => x.Id)
            .Skip(100)
            .Take(100)
            .ToList();

生成的 SQL 是:

SELECT 
    [Project1].[Id] AS [Id], 
    [Project1].[FolderPath] AS [FolderPath], 
    [Project1].[Filename] AS [Filename], 
    [Project1].[IsFlagged] AS [IsFlagged], 
    [Project1].[IsHidden] AS [IsHidden], 
    [Project1].[TextField1] AS [TextField1], 
    [Project1].[TextField2] AS [TextField2], 
    [Project1].[TextField3] AS [TextField3], 
    [Project1].[TextField4] AS [TextField4], 
    [Project1].[GroupCode] AS [GroupCode], 
    [Project1].[Deleted] AS [Deleted], 
    [Project1].[Created] AS [Created], 
    [Project1].[CreatedBy] AS [CreatedBy], 
    [Project1].[Updated] AS [Updated], 
    [Project1].[UpdatedBy] AS [UpdatedBy]
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[FolderPath] AS [FolderPath], 
        [Extent1].[Filename] AS [Filename], 
        [Extent1].[IsFlagged] AS [IsFlagged], 
        [Extent1].[IsHidden] AS [IsHidden], 
        [Extent1].[TextField1] AS [TextField1], 
        [Extent1].[TextField2] AS [TextField2], 
        [Extent1].[TextField3] AS [TextField3], 
        [Extent1].[TextField4] AS [TextField4], 
        [Extent1].[GroupCode] AS [GroupCode], 
        [Extent1].[Deleted] AS [Deleted], 
        [Extent1].[Created] AS [Created], 
        [Extent1].[CreatedBy] AS [CreatedBy], 
        [Extent1].[Updated] AS [Updated], 
        [Extent1].[UpdatedBy] AS [UpdatedBy]
        FROM [dbo].[view_ProEditBulkImageInfo] AS [Extent1]
        WHERE
            ([Extent1].[Deleted] IS NULL) 
        AND ([Extent1].[Id] >= 300000)
    )  AS [Project1]
    ORDER BY row_number() OVER (ORDER BY [Project1].[Id] ASC)
    OFFSET 100 ROWS FETCH NEXT 100 ROWS ONLY

问题似乎是第一个投影已被完全评估(大约 1000 万条记录),然后在返回前 100 行之前对其进行排序。查询执行计划将 SORT 成本显示为批处理的 92%。

这个查询大约需要 30 秒,就在超时的尖端,所以它是否会返回时时好时坏。

我正在寻找一些关于如何加快速度的提示,针对视图的查询非常快(

【问题讨论】:

  • 您确定需要在获取页面之前先进行排序吗?
  • Linq 不应该只是语法糖。 Take 和 Skip 之所以慢,是因为它们被翻译为 OFFSET 和 FETCH,它们在大量记录上本来就很慢。看看here
  • 是否需要导航到任意页面,还是按顺序导航(第1页,然后是2,然后是3...)?
  • LINQ 不会让您在没有 OrderBy 的情况下跳过。
  • 并且导航是按顺序进行的。用户通常会知道图像 Id,或者至少知道来自大约同一时间的图像的 Id,因此他们不太可能对结果进行过多的分页。

标签: c# linq-to-entities


【解决方案1】:

我写完这个问题,然后想到了一个稍微不同的方法。我想我还是会发布,因为它可能有用。

我改变了我的代码结构。我现在先做一个扩展的 .Take() 。这让我的所有页面都可以到达,包括我想要返回的页面。然后我按顺序进行并跳过以仅获取我想要的页面。

        query = query
            .Take((page.GetValueOrDefault(0) + 1) * recordCount.GetValueOrDefault(100));

        // Now skip to the required page. 
        daResults = daResults
            .OrderBy(x => x.Id)
            .Skip(page.GetValueOrDefault(0) * recordCount.GetValueOrDefault(100))
            .ToList();

原始的 Skip/Take 导致以下 SQL,它需要之前完全评估的内部查询,并且速度很慢:

ORDER BY row_number() OVER (ORDER BY [Project1].[Id] ASC)
OFFSET 100 ROWS FETCH NEXT 100 ROWS ONLY

改变它,内部查询要小得多。该内部子查询使用 SELECT TOP(200),速度快如闪电,然后将 OFFSET 等应用于缩减结果

在这一切发生之后,我仍然只是枚举结果 (.ToList()),所以它都保留在数据库中,结果现在几乎又是即时的。

【讨论】:

  • 嗯,所以当页面接近最后一页时,您将读取内存中的百万条记录只是为了获取最后 100 条?
  • 是的,虽然我刚刚尝试过没有第一次枚举,但查询又完全是数据库端。 (以上编辑)
  • 如果它对你有用,那么请将其标记为答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-21
  • 2011-07-18
  • 1970-01-01
  • 1970-01-01
  • 2011-03-11
相关资源
最近更新 更多