【问题标题】:Why does this query timeout? V2为什么这个查询超时? V2
【发布时间】:2011-03-24 10:18:45
【问题描述】:

这个问题是This Question的后续问题

解决方案,当时清除执行计划缓存似乎有效,但我一遍又一遍地遇到同样的问题,清除缓存似乎不再有帮助。这里肯定有更深层次的问题。

我发现如果我从查询中删除 .Distinct() ,它会在大约 2 秒内返回行(带有重复项)。但是,使用 .Distinct() 需要 4 分钟以上才能完成。表中有很多行,有些where子句字段没有索引。但是,返回的记录数量很少(最多几十个)。

令人困惑的部分是,如果我通过 Linqpad 获取由 Linq 查询生成的 SQL,然后将该代码作为 SQL 或在 SQL Management Studio(包括 DISTINCT)中执行,它会在大约 3 秒内执行。

Linq 查询和执行的 SQL 有什么区别?

我有一个短期的解决方法,那就是将不带 .Distinct() 的集合作为列表返回,然后在列表上使用 .Distinct,这大约需要 2 秒。但是,我不喜欢在 Web 服务器上做 SQL Server 工作。

我想了解为什么 Distinct 在 Linq 中慢了 2 个数量级,但在 SQL 中没有。

更新:

通过 Linq 执行代码时,sql profiler 显示这段代码,基本上是相同的查询。

sp_executesql N'SELECT DISTINCT [t5].[AccountGroupID], [t5].[AccountGroup] 
    AS [AccountGroup1]
FROM [dbo].[TransmittalDetail] AS [t0]
INNER JOIN [dbo].[TransmittalHeader] AS [t1] ON [t1].[TransmittalHeaderID] = 
    [t0].[TransmittalHeaderID]
INNER JOIN [dbo].[LineItem] AS [t2] ON [t2].[LineItemID] = [t0].[LineItemID]
LEFT OUTER JOIN [dbo].[AccountType] AS [t3] ON [t3].[AccountTypeID] = 
    [t2].[AccountTypeID]
LEFT OUTER JOIN [dbo].[AccountCategory] AS [t4] ON [t4].[AccountCategoryID] = 
    [t3].[AccountCategoryID]
LEFT OUTER JOIN [dbo].[AccountGroup] AS [t5] ON [t5].[AccountGroupID] = 
    [t4].[AccountGroupID]
LEFT OUTER JOIN [dbo].[AccountSummary] AS [t6] ON [t6].[AccountSummaryID] = 
    [t5].[AccountSummaryID]
WHERE ([t1].[TransmittalEntityID] = @p0) AND ([t1].[DateRangeBeginTimeID] = @p1) AND 
([t1].[ScenarioID] = @p2) AND ([t6].[AccountSummaryID] = @p3)',N'@p0 int,@p1 int,
   @p2 int,@p3 int',@p0=196,@p1=20100101,@p2=2,@p3=0

更新:

查询之间的唯一区别是Linq使用sp_executesql执行它而SSMS没有,否则查询是相同的。

更新:

我尝试了各种事务隔离级别,但均无济于事。我还设置了 ARITHABORT 在执行时尝试强制重新编译,没有区别。

【问题讨论】:

    标签: asp.net performance linq-to-sql sql-server-2008 .net-3.5


    【解决方案1】:

    糟糕的计划很可能是参数嗅探的结果:http://blogs.msdn.com/b/queryoptteam/archive/2006/03/31/565991.aspx

    不幸的是,在 L2S 中并没有任何好的通用方法(据我所知)来避免这种情况。 context.ExecuteCommand("sp_recompile ...") 如果查询不经常执行,将是一个丑陋但可能的解决方法。

    稍微改变查询以强制重新编译可能是另一个。

    将查询的部分(或全部)移动到视图*、函数*或存储过程* DB 端将是另一种解决方法。
    * = 您可以使用本地参数 (func/proc) 或优化器提示(全部三个)来强制执行“好”计划

    顺便说一句,您是否尝试过更新相关表的统计信息? SQL Server 的自动更新统计信息并不总能完成这项工作,因此除非您有计划的工作,否则可能值得考虑编写脚本和计划更新统计信息... ...根据需要调整样本大小也可以帮助。

    可能有一些方法可以通过在所涉及的表上添加*(或删除*)正确的索引来解决问题,但不知道底层数据库架构、表大小、数据分布等,这有点难以提供关于...的具体建议
    * = 缺少和/或重叠/冗余的索引都可能导致错误的执行计划。

    【讨论】:

    • 好的,所以当我添加执行计划告诉我的缺失索引时,问题就消失了。选择了更好的计划,虽然不像非参数化查询那样并行,但它的响应时间为亚秒级。
    【解决方案2】:

    Linqpad 为您提供的 SQL 可能与发送到数据库的 SQL 不完全相同。

    以下是我的建议:

    1. 在执行查询时对 DB 运行 SQL Profiler。找到与您的查询相对应的语句
    2. 将整个语句粘贴到 SSMS 中,并启用“显示实际执行计划”选项。
    3. 在此处发布生成的计划供人们剖析。

    要查找的关键内容:

    • 表扫描,这通常意味着缺少索引
    • 图形计划中的宽箭头,表示正在处理大量中间行。

    如果您使用的是 SQL 2008,查看计划通常会告诉您是否缺少任何索引,应该添加这些索引以加快查询速度。

    另外,您是否正在针对其他用户加载的数据库执行?

    【讨论】:

    • 当我查看分析器时,除了使用 sp_executesql 之外,该 sql 看起来相同。不,数据库目前没有承受重负载,尽管有时可能会如此。我正在等待查询完成以获取实际的执行计划。
    • 好的,所以执行计划是完全不同的,即使查询是相同的。从 SSMS 执行的 SQL 查询使用了大量的 Parallelism,86% 的工作在 TransmittalDetail 的 Table Scan 中,而更长的 Linq 执行计划花费 27% 的时间在“Table Spool (Lazy Spool)”中,只有 29 % 的时间在同一个表扫描中,41% 的时间在嵌套循环中。在这两种情况下,都报告了相同的缺失索引。
    【解决方案3】:

    乍一看,有很多联接,但我只能看到一件事可以立即减少数量,而无需在我面前有架构...看起来您不需要 AccountSummary。

    [t6].[AccountSummaryID] = @p3
    

    可能

    [t5].[AccountSummaryID] = @p3
    

    返回值来自 [t5] 表。 [t6] 仅用于过滤那个看起来像是从 t5 到 t6 的外键的参数,因此它存在于 [t5] 中。因此,您可以完全删除与 [t6] 的连接。还是我错过了什么?

    【讨论】:

    • 你提到这一点很有趣。通过执行优化,查询将在 25 秒内返回,而不是 4 分钟。仍然比应有的速度慢一个数量级,但这很好。
    • 这会让我相信连接的数量会影响查询性能,这可能看起来像是一个很大的“duh”,但即使有尽可能多的连接,类似的查询也不会花这么长时间。跨度>
    【解决方案4】:

    您确定要在此处使用 LEFT OUTER JOIN 吗?此查询看起来应该可能使用 INNER JOIN,尤其是因为您正在获取可能为 NULL 的列,然后对其执行不同的操作。

    【讨论】:

    • 为了回应 EchoCoder 关于隔离级别的评论,我写了一篇关于在没有任何表锁的情况下执行 LINQ 查询的便捷方法的博客文章:ryancoder.blogspot.com/2010/06/… 如果这样可以大大加快速度,则表明来自其他并发查询的表锁定是一个问题。但这并不能解释 LINQ 和 SSMS 在性能上的差异
    • 查询是由 Linq 使用 LEFT OUTER JOINS 生成的,所以我不知道有什么方法可以改变它。使用您的扩展程序也不会产生任何明显的变化。无论如何,这些表中不会有 NULLS。
    • 好吧,这里真正的问题是 LINQ 生成的是 LEFT OUTER JOIN 而不是 INNER JOIN。检查您的 DBML。您可能缺少连接子句中使用的列之间的关联。还要确保作为主键的任何列在 DBML 中都被标记为主键。然后重新生成您的 LINQ 类并重试。这可能会给您带来巨大的加速。
    【解决方案5】:

    检查您的 SSMS 会话和应用程序之间的事务隔离级别是否相同。这是我见过的相同查询之间性能差异很大的最大罪魁祸首。

    此外,通过 SSMS 工作时使用的连接属性与从应用程序或 LinqPad 执行查询时使用的连接属性不同。对 SSMS 连接的 Connection 属性和应用程序的连接进行一些检查,您应该会看到差异。在所有其他条件相同的情况下,这可能是不同的。请记住,您正在通过两个不同的应用程序执行查询,这些应用程序可以具有两种不同的配置,甚至可以使用两个不同的数据库驱动程序。如果查询相同,那么我只能看到不同之处。

    附带说明,如果您正在手工编写 SQL,您可以尝试将条件从 WHERE 子句移到适当的 JOIN 子句中。这实际上改变了 SQL Server 执行查询的方式,并且可以生成更有效的执行计划。我见过将过滤器从 WHERE 子句移到 JOIN 的情况,这会导致 SQL Server 在执行计划中更早地过滤表并显着改变执行时间。

    【讨论】:

    • 隔离级别似乎没有任何区别。我已将查询包含在 TransactionScope 中,其默认隔离级别与 SMSS (ReadCommitted) 相同,没有区别。另请注意,当我在 SMSS 中执行 sp_executesql 查询时,它也需要很长时间,但非 sp_executesql 查询不会。我想有一个区别,一个是参数化的,另一个不是。
    猜你喜欢
    • 2019-01-15
    • 1970-01-01
    • 2011-03-11
    • 2011-08-27
    • 2020-03-19
    • 2014-03-12
    • 2011-02-28
    相关资源
    最近更新 更多