解释不同 LINQ 语句之间的性能差异

Posted

技术标签:

【中文标题】解释不同 LINQ 语句之间的性能差异【英文标题】:Explain performance differences between different LINQ statements 【发布时间】:2021-09-24 18:47:53 【问题描述】:

我正试图解决以下问题:

我正在尝试优化这个查询:

(from ur in UserRates
 where ur.BillingProperty == null
    && ur.JobPosition == null
    && ur.ValidFrom.Date <= date
    && (ur.ValidTo ?? DateTime.MaxValue).Date >= date
 orderby ur.ValidFrom descending
 select ur).FirstOrDefault();

不,这最多可能需要 10 秒。

现在我尝试使用 LINQ 方法链接,并设法通过以下语句将其缩短到一秒:

UserRates.OrderByDescending(u => u.ValidFrom)
     .FirstOrDefault(u =>
         u.ValidFrom.Date <= date
     && (u.ValidTo ?? DateTime.MaxValue).Date >= date
     && u.BillingProperty == null
     && u.JobPosition == null);

然后我想知道,为什么这个 SQL 查询使用大约 0 秒来执行:

SELECT *
FROM UserRate
Where ValidFrom <= @date
  AND (ValidTo >= @date OR ValidTo IS NULL)
  AND BillingPropertyID IS NULL
  AND JobPositionId IS NULL
  AND [UserId] = @UserId

@date 和 @userId 填充了相同的值,正如代码使用的那样。

然后我切换了 LINQ 调用

UserRates.Where(u =>
      u.ValidFrom.Date <= date
  && (u.ValidTo ?? DateTime.MaxValue).Date >= date
  && u.BillingProperty == null
  && u.JobPosition == null
).OrderByDescending(u => u.ValidFrom)
 .FirstOrDefault();

再次执行大约需要 5 秒。我应该提到,我在 UserRate 表上创建了一个非聚集索引

我只是不知道差异在哪里,我感谢每一个提示或朝着正确方向的推动。

【问题讨论】:

ur.ValidFrom.Date &lt;= dateValidFrom &lt;= @date 在逻辑和性能方面都存在巨大差异。 (仅提及 LINQ 和 SQL 语句之间的一个显着区别)。 linq 语句不会被翻译成带有“where”部分的 sql select 语句,包含上述过滤器吗?老实说,我不明白你的意思。 空白不花钱,你知道的。如需更好的 SQL 性能帮助,请通过 https://brentozar.com/pastetheplan 分享相关查询计划。还请edit您的问题并添加表和索引定义。如果我不得不猜测,u.ValidFrom.Date &lt;= date(u.ValidTo ?? DateTime.MaxValue).Date &gt;= date 可能没有帮助,而且很可能FirstOrDefault 导致TOP 1 生成,这可以让编译器认为它会很快得到结果并选择一个完全不同的计划。您在 SQL 中也没有 ORDER BY,这会产生巨大的影响 ur.ValidFrom.Date 从日期中截断时间部分,因此在比较之前会对其进行更改。但更糟糕的是,它不是sargable。那是什么,我最近解释了here。史蒂夫的回答也提到了这一点,尽管没有使用 sargable 这个词。 查看生成的 SQL 至少会向您揭示它与您编写的 SQL 非常不同。在尝试解释性能差异时,这始终是第一步。 【参考方案1】:

您可以使用分析器在这两种情况下捕获准确的 SQL 语句,然后将它们的执行计划与服务器进行比较。这可能会揭示一些差异。

我可以发现可能会成为性能瓶颈的一件事是您正在比较 DateTime 列上的 Date 值。有时为了获得性能,您必须依赖系统中的约定,即“ValidFrom”之类的值将始终存储为日期,或者只是避免 SQL 端转换。

例如,如果您想确保 ValidFrom 落在日期:

var validFrom = date.AddDays(1);

var latestUserRate = context.UserRates
    .Where(ur => ur.BillingProperty == null 
        && ur.JobPosition == null
        && ur.ValidFrom < validFrom
        && (!ur.ValidTo.HasValue || ur.ValidTo >= date))
    .OrderByDescending(ur => ur.ValidFrom)
    .FirstOrDefault();

不进行.Date 比较的要点是,服务器可以有效地利用表上的索引。通常,您对表格中的值进行的“调整”越少越好。

相似查询之间的执行时间差异可以归结为索引使用和执行计划选择等因素。有时这确实需要捕获有问题的 SQL 以仔细查看,然后研究推荐的索引更改和可能的替代方案,以消除生成不良的语句。

【讨论】:

不要忘记 EF 会为您提供 SQL,您不必再使用分析器了 :) 这将是显示 OP 的有用建议,以便他们将来可以自己调试它

以上是关于解释不同 LINQ 语句之间的性能差异的主要内容,如果未能解决你的问题,请参考以下文章

插入和不存在的插入之间的性能差异

C# 在 Linq 查询 WHERE 语句中返回两个纬度/经度坐标之间的计算距离

由于 .net-core 中的 linq-statement 导致 IEnumerable 和 List 之间出现意外差异? [复制]

用户定义函数和存储过程之间的性能差异

使用 Linq 替换 foreach 语句

?:运营商与。 If 语句性能