【问题标题】:Linq slowness materializing complex queries实现复杂查询的 Linq 缓慢
【发布时间】:2015-07-30 13:21:39
【问题描述】:

我经常发现,如果我在 Linq 查询中有太多的连接(无论是使用实体框架还是 NHibernate)和/或生成的匿名类的形状太复杂,Linq 需要很长时间才能实现结果设置成对象。

这是一个通用问题,但这里有一个使用 NHibernate 的具体示例:

var libraryBookIdsWithShelfAndBookTagQuery = (from shelf in session.Query<Shelf>()
    join sbttref in session.Query<ShelfBookTagTypeCrossReference>() on
         shelf.ShelfId equals sbttref.ShelfId
    join bookTag in session.Query<BookTag>() on
         sbttref.BookTagTypeId equals (byte)bookTag.BookTagType
    join btbref in session.Query<BookTagBookCrossReference>() on
         bookTag.BookTagId equals btbref.BookTagId
    join book in session.Query<Book>() on
         btbref.BookId equals book.BookId
    join libraryBook in session.Query<LibraryBook>() on
         book.BookId equals libraryBook.BookId
    join library in session.Query<LibraryCredential>() on
         libraryBook.LibraryCredentialId equals library.LibraryCredentialId
    join lcsg in session
         .Query<LibraryCredentialSalesforceGroupCrossReference>()
          on library.LibraryCredentialId equals lcsg.LibraryCredentialId
    join userGroup in session.Query<UserGroup>() on
         lcsg.UserGroupOrganizationId equals userGroup.UserGroupOrganizationId
    where
         shelf.ShelfId == shelfId &&
         userGroup.UserGroupId == userGroupId &&
         !book.IsDeleted &&
         book.IsDrm != null &&
         book.BookFormatTypeId != null
    select new
    {
        Book = book,
        LibraryBook = libraryBook,
        BookTag = bookTag
    });

// add a couple of where clauses, then...
var result = libraryBookIdsWithShelfAndBookTagQuery.ToList();

我知道这不是查询执行,因为我在数据库上放置了一个嗅探器,我可以看到查询需要 0 毫秒,但代码需要大约一秒钟来执行该查询并带回所有 11 条记录。

所以是的,这是一个过于复杂的查询,在 9 个表之间有 8 个连接,我可能可以将它重组为几个较小的查询。或者我可以把它变成一个存储过程——但这会有帮助吗?

我想了解的是,在高性能查询和开始难以实现的查询之间的红线在哪里?引擎盖下发生了什么?如果这是一个 SP,我随后在内存中将其平面结果操纵成正确的形状,这会有所帮助吗?

编辑:响应 cmets 中的请求,发出的 SQL 如下:

SELECT DISTINCT book4_.bookid                 AS BookId12_0_, 
                libraryboo5_.librarybookid    AS LibraryB1_35_1_, 
                booktag2_.booktagid           AS BookTagId15_2_, 
                book4_.title                  AS Title12_0_, 
                book4_.isbn                   AS ISBN12_0_, 
                book4_.publicationdate        AS Publicat4_12_0_, 
                book4_.classificationtypeid   AS Classifi5_12_0_, 
                book4_.synopsis               AS Synopsis12_0_, 
                book4_.thumbnailurl           AS Thumbnai7_12_0_, 
                book4_.retinathumbnailurl     AS RetinaTh8_12_0_, 
                book4_.totalpages             AS TotalPages12_0_, 
                book4_.lastpage               AS LastPage12_0_, 
                book4_.lastpagelocation       AS LastPag11_12_0_, 
                book4_.lexilerating           AS LexileR12_12_0_, 
                book4_.lastpageposition       AS LastPag13_12_0_, 
                book4_.hidden                 AS Hidden12_0_, 
                book4_.teacherhidden          AS Teacher15_12_0_, 
                book4_.modifieddatetime       AS Modifie16_12_0_, 
                book4_.isdeleted              AS IsDeleted12_0_, 
                book4_.importedwithlexile     AS Importe18_12_0_, 
                book4_.bookformattypeid       AS BookFor19_12_0_, 
                book4_.isdrm                  AS IsDrm12_0_, 
                book4_.lightsailready         AS LightSa21_12_0_, 
                libraryboo5_.bookid           AS BookId35_1_, 
                libraryboo5_.libraryid        AS LibraryId35_1_, 
                libraryboo5_.externalid       AS ExternalId35_1_, 
                libraryboo5_.totalcopies      AS TotalCop5_35_1_, 
                libraryboo5_.availablecopies  AS Availabl6_35_1_, 
                libraryboo5_.statuschangedate AS StatusCh7_35_1_, 
                booktag2_.booktagtypeid       AS BookTagT2_15_2_, 
                booktag2_.booktagvalue        AS BookTagV3_15_2_ 
FROM   shelf shelf0_, 
       shelfbooktagtypecrossreference shelfbookt1_, 
       booktag booktag2_, 
       booktagbookcrossreference booktagboo3_, 
       book book4_, 
       librarybook libraryboo5_, 
       library librarycre6_, 
       librarycredentialsalesforcegroupcrossreference librarycre7_, 
       usergroup usergroup8_ 
WHERE  shelfbookt1_.shelfid = shelf0_.shelfid 
       AND booktag2_.booktagtypeid = shelfbookt1_.booktagtypeid 
       AND booktagboo3_.booktagid = booktag2_.booktagid 
       AND book4_.bookid = booktagboo3_.bookid 
       AND libraryboo5_.bookid = book4_.bookid 
       AND librarycre6_.libraryid = libraryboo5_.libraryid 
       AND librarycre7_.librarycredentialid = librarycre6_.libraryid 
       AND usergroup8_.usergrouporganizationid = 
           librarycre7_.usergrouporganizationid 
       AND shelf0_.shelfid = @p0 
       AND usergroup8_.usergroupid = @p1 
       AND NOT ( book4_.isdeleted = 1 ) 
       AND ( book4_.isdrm IS NOT NULL ) 
       AND ( book4_.bookformattypeid IS NOT NULL ) 
       AND book4_.lightsailready = 1 

编辑 2:这是来自 ANTS Performance Profiler 的性能分析:

【问题讨论】:

  • 如果“//添加几个where子句,然后...”部分被省略,你会得到多少结果?
  • @VitaliyKalinin,嗯...更多?但为什么这很重要?
  • 这是一个有趣的问题。你能告诉我们生成的 SQL 吗?您是否尝试过使用 NHibernate 的 QueryOver 语法执行相同的查询?
  • @TyreeJackson 查看我的最新编辑。性能问题显然不是来自 SQL。它返回 11 行。
  • @ShaulBehr 根据我在您的更新中阅读的内容,CreateQuery 方法似乎占用了大部分执行时间。您能否在包含框架方法调用的调用树中以毫秒为单位从 ANTS 获得精确的计时测量?

标签: c# performance linq entity-framework nhibernate


【解决方案1】:

在视图中放置大量连接或超级普通连接通常是数据库的“好”做法。 ORM 不会让您忽略这些事实,也不会补充花费数十年时间微调数据库来有效地完成这些事情。将这些连接重构为单个视图或几个视图,如果这在您的应用程序的更大视角中更有意义的话。

NHibernate 应该优化查询并减少数据,以便 .Net 只需要处理重要部分。但是,如果这些领域对象自然而然地很大,那仍然是大量数据。此外,如果就返回的行而言它是一个非常大的结果集,那么即使数据库能够快速返回该集合,也会有很多对象被实例化。将此查询重构为仅返回您实际需要的数据的视图也将减少对象实例化开销。

另一个想法是不做.ToList()。返回可枚举并让您的代码懒惰地使用数据。

【讨论】:

  • 我在专门的海量数据 SQL 集中使用 LINQ 和 EF,虽然如果您不明确,它有时会引入不必要的开销,但您将遇到的大部分减速将是客户端,而不是 SQL 端。不过,我要补充一点,如果您正在创建视图,请不要 -DO。不要-让它们被其他任何东西使用。我不得不在复杂的视图中无限地绘制复杂的视图,而这使 SQL 慢到爬行。
  • 我的问题是原则性问题之一:我的大多数查询都可以正常工作,您甚至不会感觉到具体化的问题。但随后您在某处越过一条线,物化开始为查询增加实际成本。为什么?为什么从具有 8 个连接的 Linq 查询更改为没有连接的 SP 或视图会有所帮助?它仍然是相同的平面数据被按摩到相同的匿名类结果集中。
  • @ShaulBehr 您没有提供足够的细节来获得详细的答案。例如,Book 是什么样的?它有任何导航属性吗?其他人呢?是否有任何循环引用问题?公平地说,我不像 EF 那样 100% 了解 NHibernate 的内部结构,所以如果下一部分没有意义,那就这样吧。您仍然要求缓存机制加载并在许多类型的对象之间进行匹配,然后合成一个组合。 ORM 的面包和黄油绝对是关于事物如何关联的元信息,以便他们可以完成工作。
  • 通过大众投票,我给你赏金。实际的解决方案是我们只重构查询以返回最少的列数。
  • 这就是我的想法,也是我推荐该视图的原因,因为您的所有加入。我很高兴你解决了!
【解决方案2】:

根据分析信息,CreateQuery 占用了总执行时间的 45%。但是,正如您提到的,当您直接执行时,查询需要 0 毫秒。但仅凭这一点还不足以说明存在性能问题,因为,

  1. 您正在使用分析器运行查询,这对执行时间有重大影响。
  2. 当您使用分析器时,它会影响正在分析的每个代码,但不会影响 sql 执行时间(因为它发生在 SQL 服务器中),因此您可以看到其他一切都比 SQL 语句慢。

所以理想的场景是测量执行整个代码块需要多长时间,测量 SQL 查询的时间和计算时间,如果你这样做了,你最终可能会得到不同的值。

但是,我并不是说 NH Linq to SQL 实现针对您提出的任何查询进行了优化,但是 NHibernate 中还有其他方法可以处理这些情况,例如 QueryOverAPI、CriteriaQueries、HQL,最后是 SQL .

  1. 执行查询和查询之间的红线在哪里? 一个开始与物化作斗争的人。引擎盖下发生了什么?

这是一个相当难的问题,如果没有 NHibernate Linq to SQL 提供程序的详细知识,很难提供准确的答案。您可以随时尝试提供的不同机制,看看哪一种最适合给定场景。

  1. 如果这是一个 SP,我随后的结果平平,会有帮助吗 在记忆中操纵成正确的形状?

是的,使用 SP 可以帮助事情快速运行,但使用 SP 会给您的代码库增加更多维护问题。

【讨论】:

    【解决方案3】:

    你有一般的问题,我会告诉你一般的答案:)

    1. 如果您查询数据以供读取(而不是用于更新),请尝试使用匿名类。原因是 - 它们更容易创建,它们没有导航属性。你只选择你需要的数据!这是非常重要的规则。所以,试着用这样的smth替换你的选择:

      select new { Book = new { book.Id, book.Name}, LibraryBook = new { libraryBook.Id, libraryBook.AnotherProperty}, BookTag = new { bookTag.Name} }

    2. 存储过程很好,当查询复杂,linq-provider 生成无效代码时,可以用普通的 SQL 或存储过程代替。这并不常见,我认为这不是你的情况

    3. 运行您的 sql 查询。它返回多少行?它与结果的值相同吗?有时 linq 提供程序会生成代码,选择更多行来选择一个实体。当实体与另一个选择实体具有一对多关系时,就会发生这种情况。例如:

    class Book { int Id {get;set;} string Name {get;set;} ICollection<Tag> Tags {get;set;} } class Tag { string Name {get;set;} Book Book {get;set;} } ... dbContext.Books.Where(o => o.Id == 1).Select(o=>new {Book = o, Tags = o.Tags}).Single(); 我只选择一本 Id = 1 的书,但提供者会生成代码,返回行数量等于标签数量(实体框架会这样做)。

    1. 将复杂查询拆分为一组简单查询并在客户端加入。有时,您有许多条件的复杂查询,结果 sql 变得很糟糕。因此,您将大查询拆分为更简单的查询,获取每个查询的结果并在客户端加入/过滤。

    最后,我建议你使用匿名类作为选择的结果。

    【讨论】:

      【解决方案4】:

      Don’t use Linq’s Join. Navigate!

      在那篇文章中你可以看到:

      只要数据库中有适当的外键约束,导航属性就会自动创建。也可以在 ORM 设计器中手动添加它们。与所有 LINQ to SQL 的使用一样,我认为最好专注于正确使用数据库并让代码准确反映数据库结构。将关系正确指定为外键后,代码可以安全地假设表之间的引用完整性。

      【讨论】:

      • 我 100% 同意这一点。我们确实希望开始朝这个方向发展(尽管可能需要一些时间和新的发布周期)。但是您认为它会提高性能吗?
      • 它可能会根据您的数据库结构进行一些优化。
      【解决方案5】:

      我 100% 同意其他人表达的观点(关于它们是这里优化的两个部分,而 SQL 执行是一个很大的未知数,很可能是性能不佳的原因)。

      解决方案的另一部分可能会帮助您加快速度,即预编译您的 LINQ 语句。我记得这是对我多年前从事的一个小项目(高流量)的巨大优化......似乎它会导致您看到的客户端缓慢。说了这么多,尽管从那以后我还没有发现需要使用它们……所以首先要注意其他人的警告! :)

      https://msdn.microsoft.com/en-us/library/vstudio/bb896297(v=vs.100).aspx

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-04-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多