【问题标题】:SQL adding Order By clause causes the query to run significantly faster. Explanation neededSQL 添加 Order By 子句会使查询运行得更快。需要解释
【发布时间】:2021-01-09 08:10:36
【问题描述】:

所以我有这个查询:

SELECT * 
FROM ViewTechnicianStatus
WHERE NotificationClass = 2 
    AND MachineID IN (SELECT ID FROM MachinesTable WHERE DepartmentID = 1 AND IsMachineActive <> 0)
--ORDER BY ResponseDate DESC

视图庞大而复杂,包含许多连接和子查询。当我运行此查询时,它需要永远完成,但是如果我添加 ORDER BY 它会立即完成并按预期返回 20 行。我不明白如何添加 ORDER BY 会对性能产生如此巨大的积极影响。如果有人可以向我解释这种现象,我会很高兴。

编辑: 这是带有SET STATISTICS TIME, IO ON; 标志的概要。抱歉隐藏的表名,但我认为我不能公开这些。

没有 ORDER BY

订购方式

【问题讨论】:

  • 您确定您的观察结果确实具有可重复性吗?添加ORDER BY 本身不应该,AFIAK,永远更快 进行查询。如果你也使用TOP,那么它可能会改变运行时间。
  • @TimBiegeleisen 我在 3 个不同的客户端(相同的数据结构,不同的数据)上运行了这个查询,性能差异是相同的,所以我可以确定它是可重现的。
  • 我想你已经这样做了,但鉴于这是一个奇怪的情况,最好确认一下 - 你是否以不同的顺序多次运行这些查询(例如,首先在不同的数据库上运行包括排序?可能是数据被缓存了,或者第一次创建执行计划需要很长时间(我在这里采摘稻草)。在运行这两个之前,另一个故障排除步骤是SET STATISTICS TIME, IO ON;,并且看看在两个查询中所做的事情之间是否有任何重大差异。
  • @seanb 编辑了帖子以包含SET STATISTICS TIME, IO ON; 结果。请看一下
  • 问题太模糊无法回答,这种事情偶尔会碰巧发生。它可能有错误的基数估计,导致它使用嵌套循环,order by 鼓励合并连接,例如

标签: sql sql-server-2008


【解决方案1】:

为了回答您的问题,添加 order by 时查询运行速度更快的原因是由于 INDEXING。可能在您测试的所有客户端中,对这些特定字段/表都有索引,并且使用 Order by 可以提高性能。

【讨论】:

  • 我们确实使用索引。您是否可以向我提供任何资源来解释添加 Order By 如何导致查询在索引时运行得更快,因为我找不到任何东西。
  • 查看统计数据,很明显它使用了不同的计划 - 可能第二个使用索引而原始没有。特别是它使用表 6 和表 7 的方式是不同的——大约有数千次扫描,总共 3 次,每次读取 300 万次,每次读取 3000 次。涉及表 6 和表 7 的任何视图是否有“top”子句或 order bys?
  • @seanb 该视图没有任何 TOPs 或 Order Bys,它有一个类似于伪代码:SELECT * FROM Table6 inner join Table7 WHERE Table7 ID in (SELECT MAX(ID) FROM Table7 inner join Table6)
【解决方案2】:

总结

好的.. 我已经考虑了一段时间,因为我认为这是一个有趣的问题。我相信这在很大程度上是一个边缘案例——这是让它变得有趣的部分原因。

我正在根据所提供的信息进行有根据的猜测 - 显然,由于无法看到/玩弄它,我无法确定。但我认为这种解释与基于您提供的信息和统计数据的证据相符。

我认为主要问题是糟糕的查询计划。在没有排序的版本中,它使用了不合适的嵌套循环;在带有排序的版本中,它会(比如)进行哈希匹配或合并连接。

我发现 SQL Server 在引用其他视图的复杂视图中经常遇到查询计划问题,尤其是如果这些子视图具有分组依据/排序/等。

为了演示可能发生的差异,我将您的复杂视图简化为 2 个子组,我将其称为“视图组”(可能是一个或多个视图和表格 - 不是技术术语,只是一个术语总结一下)。

  • 第一个视图组包含大多数表,
  • 第二个视图组包含从表 6 和表 7 获取数据的视图。

对于这两种方法,SQL 如何使用视图组中的数据可能是相同的(例如,使用相同的索引等)。但是,它在两个视图组之间进行连接的方法有所不同。

示例 - 查询规划器低估了视图组 2 的成本,并不关心使用哪种方法

我猜

  • 第一个视图组,在连接点,正在处理大约 3000 行(它还没有过滤掉它),并且
  • 查询生成器认为视图组 2 易于运行

没有 order by 的版本中,查询计划设计为带有nested loop 连接。也就是说,它获取视图组 1 中的每个值,然后针对每个值运行视图组 2 以获取相关数据。这意味着视图组 2 运行了 3000 次(视图组 1 中的每个值运行一次)。

with 版本中,它决定在视图组 1 和视图组 2 之间进行(比如说)哈希匹配。这意味着它只需要运行视图组 2 一次,但是多花点时间整理一下。但是,因为您要求对它进行排序,所以它选择了哈希匹配。

但是,由于设计的查询低估了视图组 2 的成本,事实证明哈希匹配对于这种情况来说是一个更好的查询计划。

示例 - 查询计划器使用缓存计划

我相信(但可能是错误的!)当您在视图中引用视图时,它通常可以只为子视图使用缓存计划,而不是尝试为您当前的情况获取最佳计划。

可能您的一个视图使用“缓存计划”,而另一个视图尝试优化包括子视图在内的查询计划。

具有讽刺意味的是,with order by 的查询版本可能更复杂,在这种情况下,它使用视图组 2 的缓存计划。但是,它知道它没有优化了视图组 2 的计划,它只为视图组 2 获取一次数据,然后将所有结果保存在内存中并在哈希匹配中使用。

相比之下,在没有 order by 的版本中,它会尝试优化查询计划(包括优化它如何使用视图),并且把它弄得一团糟。

可能的解决方案

这些都是可能性——它们可能会使情况变得更好,也可能使情况变得更糟!请注意,SQL 是一种声明性语言(您告诉计算机要做什么/想要什么,而不是如何去做)。

这不是一个完整的可能性列表,但它们是您可以尝试的事情

  • 预先计算全部或部分视图(例如,将表 6 和 7 中预先计算的内容放入临时表中,然后在视图中使用临时表)
  • 简化 SQL 和/或将所有 SQL 移动到不调用其他视图的单个视图中
  • 使用连接提示,例如,在适当的位置使用 INNER HASH JOIN 代替 INNER JOIN
  • 使用选项(重新编译)

【讨论】:

  • PS 任何其他解释都需要解释为什么第一个版本对表 6 和 7 进行了 3000 多次扫描,而第二个版本总共只进行了 3 次(对表 6 进行了 2 次扫描,对表 6 进行了 1 次扫描)表 7)。事实上,我认为我上面的答案(总结)是“当访问表 6 和表 7 时,版本 1 使用嵌套循环,而版本 2 使用哈希匹配或类似”。其余的解释为什么它可能会这样做。
  • 感谢您的解释,我会尝试使用可能的解决方案。
  • 谢谢...正如我所说,这是一个有趣的案例,我很想知道这些是否能解决这些问题。您可以查看执行计划以查看它们是否匹配。如果是我,我会先尝试使用explicit join hint to make it a hash join,但这确实取决于其他进程使用这些视图。
猜你喜欢
  • 1970-01-01
  • 2021-09-01
  • 2011-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-24
相关资源
最近更新 更多