【问题标题】:Linq To SQL, why the huge performance difference between two similar queries that are returning the same number of recordsLinq To SQL,为什么返回相同数量记录的两个相似查询之间存在巨大的性能差异
【发布时间】:2020-06-25 12:51:03
【问题描述】:

我有两个查询,第一个返回所有“打开”记录,其中回复日期尚未过去,第二个返回所有“打开未查看”记录,其中回复日期尚未过去,记录有未被用户查看(表 RfqVieweds 中没有条目)。在这种情况下,用户没有查看 1000 条记录中的任何一条,因此返回了所有 1000 条记录。每个查询的倒数第二行(就在“select new RfqDto()”之前)是两个查询之间的差异。

第一个查询需要大约 45 秒才能返回 1000 条记录。第二个查询大约需要 4 秒才能返回相同的 1000 条记录。为什么?如何让第一个查询与第二个查询一样快?

查询 1:

var groupQuery = Rfqs.Where(rfqs => rfqs.IsPrimaryEmail && (rfqs.Contract != "Upstream"))
    .GroupBy(rfqs => new {rfqs.RFQ_RFISeqNum})
    .Select(g => new {g.Key.RFQ_RFISeqNum, RfqId = g.Max(p => p.RfqId)});
        
var query = (from m in groupQuery
    join t in Rfqs on new {m.RfqId} equals new {t.RfqId}
    join s in RfqSupplementals on new {RfqSeqNumber = t.RFQ_RFISeqNum} equals new {s.RfqSeqNumber}         
    from rfqViewed in RfqVieweds.Where(rv => rv.RfqId == t.RfqId && rv.SalesRep == salesrep).DefaultIfEmpty()
    from rfqStarred in RfqUserStarreds.Where(rs => rs.RfqSeqNumber == t.RFQ_RFISeqNum && rs.SalesRep == salesrep).DefaultIfEmpty()
    let rcn = RfqCommentNotifications.Where(r => r.RfqSequenceNum == t.RFQ_RFISeqNum && r.SalesRep == salesrep).Select(d => d.LastViewed).FirstOrDefault()
    let ch = RfqsChangeHistories.Where(r => r.RfqRfiSeqNum == t.RFQ_RFISeqNum && chgHistList.Contains(r.Action)).OrderByDescending(r => r.Id).FirstOrDefault()
    where (isRfqUser == false || ((t.assignedTo == salesrep || t.secndAssignedTo == salesrep || t.addlAssignedTo == salesrep)
           || (agencies.Contains(t.Agency) && (t.assignedTo == null || t.assignedTo.Trim() == string.Empty)
           && (t.secndAssignedTo == null || t.secndAssignedTo.Trim() == string.Empty) && (t.addlAssignedTo == null || t.addlAssignedTo.Trim() == string.Empty))))
           && (!isRfqUser || t.HouseOpportunity == false)
           && t.IsDeleted == false && t.IsPrimaryEmail
           && (t.ReplyByDate > now || (t.ReplyByDate == null && t.IsManualRfq))
    select new RfqDto()

查询 2:

var groupQuery = Rfqs.Where(rfqs => rfqs.IsPrimaryEmail && (rfqs.Contract != "Upstream"))
.GroupBy(rfqs => new {rfqs.RFQ_RFISeqNum})
.Select(g => new {g.Key.RFQ_RFISeqNum, RfqId = g.Max(p => p.RfqId)});
    
var query = (from m in groupQuery
join t in Rfqs on new {m.RfqId} equals new {t.RfqId}
join s in RfqSupplementals on new {RfqSeqNumber = t.RFQ_RFISeqNum} equals new {s.RfqSeqNumber}
from rfqViewed in RfqVieweds.Where(rv => rv.RfqId == t.RfqId && rv.SalesRep == salesrep).DefaultIfEmpty()
from rfqStarred in RfqUserStarreds.Where(rs => rs.RfqSeqNumber == t.RFQ_RFISeqNum && rs.SalesRep == salesrep).DefaultIfEmpty()
let rcn = RfqCommentNotifications.Where(r => r.RfqSequenceNum == t.RFQ_RFISeqNum && r.SalesRep == salesrep).Select(d => d.LastViewed).FirstOrDefault()
let ch = RfqsChangeHistories.Where(r => r.RfqRfiSeqNum == t.RFQ_RFISeqNum && chgHistList.Contains(r.Action)).OrderByDescending(r => r.Id).FirstOrDefault()
where (isRfqUser == false || ((t.assignedTo == salesrep || t.secndAssignedTo == salesrep || t.addlAssignedTo == salesrep) 
       || (agencies.Contains(t.Agency) && (t.assignedTo == null || t.assignedTo.Trim() == string.Empty) 
       && (t.secndAssignedTo == null || t.secndAssignedTo.Trim() == string.Empty) && (t.addlAssignedTo == null || t.addlAssignedTo.Trim() == string.Empty))))
       && (!isRfqUser || t.HouseOpportunity == false)
       && t.IsDeleted == false && t.IsPrimaryEmail
       && (rfqViewed == null && (t.ReplyByDate > now || (t.ReplyByDate == null && t.IsManualRfq))) 
select new RfqDto()

【问题讨论】:

  • 每种情况下返回多少行数据?查询时间是数据库大小和传输数据量的函数。两个查询都必须搜索数据库中的整个表,所以我认为数据库的大小不会导致时间差异。因此,第一个查询返回的数据似乎比第二个查询少。数据正在进入内存,因此我会在两个查询运行时查看任务管理器并检查正在使用的内存量。
  • 这不会回答这个问题,但是对于如此复杂和庞大的查询,考虑使用视图或存储过程,以便数据库可以做一些优化。
  • @jdweng 两个查询都返回 1000 行。两个查询之间的唯一区别是第二个检查是否 rfqViewed == null 而第一个不检查。
  • rfqViewed 是一个密钥吗?你是对的,性能上不应该有很大的差异。数据库最近是否进行了碎片整理?
  • @jdweng 不,rfqViewed 是“来自 RfqVieweds 中的 rfqViewed ...DefaultIfEmpty()”的结果。如果 reqViewed 为空,则用户尚未查看该 Rfqs 记录,否则他们已查看该 Rfqs 记录。

标签: c# performance linq-to-sql


【解决方案1】:

您可能需要查看查询计划以了解时间差异的真正原因,因为这取决于实际数据。

这两个查询是不同的,如果它们恰好返回相同的行数,那么Sql将无法提前知道。

我可以举一个可能导致差异的情况的例子。

首先,让我们将查询简化为

var query = (from m in groupQuery
            join t in Rfqs on new { m.RfqId } equals new { t.RfqId }
            from rfqViewed in RfqVieweds.Where(rv => rv.RfqId == t.RfqId && rv.SalesRep == salesrep).DefaultIfEmpty()
            where  
                t.ReplyByDate > now 
            select new RfqDto()

现在假设您在 Rfqs 中有 100 万行,并且将来只有 1000 条记录具有 ReplyByDate 值。如果 ReplyByDate 没有被索引,那么这意味着 sql 在返回选定的 1000 行之前必须读取并检查所有百万行。

但是如果查询变成了

var query = (from m in groupQuery
            join t in Rfqs on new { m.RfqId } equals new { t.RfqId }
            from rfqViewed in RfqVieweds.Where(rv => rv.RfqId == t.RfqId && rv.SalesRep == salesrep).DefaultIfEmpty()
            where  
                (t.ReplyByDate > now  
                && rfqViewed == null 
            select new RfqDto()

如果碰巧Rfqs中只有10000行在RfqVieweds中没有记录,并且如果rfqViewed有一个合适的索引,Sql可以快速确定,那意味着Sql只需要查看这些10000 条记录,可能会更快。

注意,如果将来有任何带有ReplyDates 的行已经过审核,那么这两个查询将不会返回相同的记录。

【讨论】:

    猜你喜欢
    • 2014-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 2021-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多