Linq缓慢实现复杂查询

时间:2015-07-30 13:21:39

标签: c# performance linq entity-framework nhibernate

我经常发现,如果我在Linq查询中有太多连接(无论是使用Entity Framework还是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();

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

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

我想要了解的是,在一个高效的查询和一个开始与物化相抗争的问题之间划线的红线在哪里?引擎盖下发生了什么?如果这是一个SP,它的平坦结果我随后会在记忆中操纵到正确的形状会有帮助吗?

编辑:以回应评论中的请求,这里是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的性能分析:

Performance analysis by ANTS

5 个答案:

答案 0 :(得分:6)

通常是数据库&#34;好&#34;练习将大量连接或超级公共连接放入视图中。 ORM不会让你忽略这些事实,也不会补充数十年来微调数据库以有效地完成这些事情的时间。如果从你的应用程序的更大角度来看,那些重新构成了一个单一的视图或几个视图。

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

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

答案 1 :(得分:3)

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

  1. 您正在使用分析器运行查询,这会对执行时间产生重大影响。
  2. 当您使用分析器时,它将影响正在分析的每个代码但不影响sql执行时间(因为它发生在SQL服务器中),因此您可以看到与SQL语句相比其他所有代码都较慢。
  3. 如此理想的情况是测量执行整个代码块所需的时间,测量SQL查询的时间和计算时间,如果这样做,您可能会得到不同的值。

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

    1.   

      在性能和查询之间交叉的红线在哪里   一个开始与物化斗争的人。引擎盖下发生了什么?

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

      1.   

        如果这是一个SP,我随后会得到平坦的结果会有所帮助   在记忆中操纵到正确的形状?

      2. 是的,使用SP有助于提高工作效率,但使用SP会给代码库带来更多维护问题。

答案 2 :(得分:2)

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

  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-query。它返回多少行?它的结果是否相同?有时linq提供程序生成代码,选择更多行来选择一个实体。当实体与另一个选择实体具有一对多关系时,就会发生这种情况。例如:
  4. 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变得很糟糕。因此,您将大查询拆分为更简单,获取每个查询的结果并在客户端加入/过滤。
    2. 最后,我建议您使用匿名类作为select的结果。

答案 3 :(得分:2)

Don’t use Linq’s Join. Navigate!

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

  

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

答案 4 :(得分:0)

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

可能有助于提高速度的解决方案的另一部分是预编译LINQ语句。我记得这是对我在很久以前工作的一个小项目(高流量)的巨大优化......似乎它会导致客户端慢慢看到你。尽管如此,虽然我没有发现需要使用它们......所以首先要注意其他人的警告! :)

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