SQL 添加 Order By 子句会使查询 运行 显着加快。需要解释
SQL adding Order By clause causes the query to run significantly faster. Explanation needed
所以我有这个查询:
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 它立即完成并且 returns 20 行按预期完成。我不明白添加 ORDER BY 怎么会对性能产生如此巨大的积极影响。如果有人能向我解释这个现象,我会很高兴。
编辑:
这是带有 SET STATISTICS TIME, IO ON;
标志的 运行down。抱歉隐藏了 table 个名字,但我认为我不能公开这些名字。
没有ORDER BY
按顺序排列
回答您的问题,您的查询在添加订单依据时运行速度更快的原因是索引。可能在您测试的所有客户端中,都为那些特定的 fields/tables 建立了索引,并且使用 Order by 使性能更好。
总结
好的..我已经考虑了一段时间,因为我认为这是一个有趣的问题。我相信这在很大程度上是一个边缘案例 - 这是让它变得有趣的一部分。
我正在根据提供的信息进行有根据的猜测 - 显然,由于无法看到 it/play,我无法确定。但我认为这个解释符合基于你提供的信息和统计数据的证据。
我认为主要问题是查询计划不佳。在没有排序的版本中,它使用了不合适的嵌套循环;在带有排序的版本中,它执行(比如)哈希匹配或合并连接。
我发现 SQL 服务器在引用其他视图的复杂视图中的查询计划经常有问题,特别是如果那些 sub-views 有组 bys/sorts/etc.
为了演示可能发生的差异,我会将您的复杂视图简化为 2 个子组,我将调用 'view groups'(可能是一个或多个视图和 tables - 不是一个技术术语,只是一个总结它们的术语。
- 第一个视图组包含最多 table 个,
- 第二个视图组包含从 tables 6 和 7 获取数据的视图。
对于这两种方法,SQL 如何使用视图组中的数据可能是相同的(例如,使用相同的索引等)。但是,它在两个视图组之间进行连接的方法有所不同。
示例 - 查询规划器低估了视图组 2 的成本并且不关心使用哪种方法
我猜
- 第一个视图组在连接点处理大约 3000 行(它还没有过滤下来),并且
- 查询生成器认为视图组 2 很容易 运行
在没有 order by 的版本中,查询计划设计有nested loop
连接。也就是说,它获取视图组 1 中的每个值,然后针对每个值,它 运行 在视图组 2 中获取相关数据。这意味着视图组 2 是 运行 3000 次(视图组 1 中的每个值一次)。
在版本中,它决定在视图组1和视图组2之间进行(比方说)哈希匹配。这意味着它只需要运行 查看组 2 一次,但要花更多时间对其进行排序。但是,因为您要求对它进行排序,所以它选择了哈希匹配。
但是,由于设计的查询低估了视图组 2 的成本,事实证明哈希匹配是一种更好的查询计划。
示例 - 查询计划器使用缓存计划
我相信(但可能是错误的!)当您在视图中引用视图时,它通常可以只使用 sub-views 的缓存计划,而不是尝试为您的当前情况获得最佳计划。
可能是您的一个视图使用了“缓存计划”,而另一个试图优化查询计划,包括 sub-views。
具有讽刺意味的是,查询版本 with order by 可能更复杂,在这种情况下它使用视图组 2 的缓存计划。但是,因为它知道它没有优化视图组 2 的计划,它只是为视图组 2 获取一次数据,然后将所有结果保存在内存中并在哈希匹配中使用它。
相比之下,在没有 order by 的版本中,它尝试优化查询计划(包括优化它使用视图的方式),并把它弄得一团糟。
可能的解决方案
这些都是可能性 - 它们可能使情况变得更好,也可能使情况变得更糟!请注意,SQL 是一种声明性语言(您告诉计算机 do/what 您想要什么,而不是如何去做)。
这不是一份完整的可能性列表,但您可以尝试这些方法
- Pre-calculate 全部或部分视图(例如,将 tables 6 和 7 中的 pre-calculated 内容放入临时 table,然后在视图中使用临时 tables)
- 简化 SQL and/or 将所有 SQL 移动到不调用其他视图的单个视图中
- 使用连接提示,例如,在适当的位置使用 INNER HASH JOIN 而不是 INNER JOIN
- 使用选项(重新编译)
所以我有这个查询:
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 它立即完成并且 returns 20 行按预期完成。我不明白添加 ORDER BY 怎么会对性能产生如此巨大的积极影响。如果有人能向我解释这个现象,我会很高兴。
编辑:
这是带有 SET STATISTICS TIME, IO ON;
标志的 运行down。抱歉隐藏了 table 个名字,但我认为我不能公开这些名字。
没有ORDER BY
按顺序排列
回答您的问题,您的查询在添加订单依据时运行速度更快的原因是索引。可能在您测试的所有客户端中,都为那些特定的 fields/tables 建立了索引,并且使用 Order by 使性能更好。
总结
好的..我已经考虑了一段时间,因为我认为这是一个有趣的问题。我相信这在很大程度上是一个边缘案例 - 这是让它变得有趣的一部分。
我正在根据提供的信息进行有根据的猜测 - 显然,由于无法看到 it/play,我无法确定。但我认为这个解释符合基于你提供的信息和统计数据的证据。
我认为主要问题是查询计划不佳。在没有排序的版本中,它使用了不合适的嵌套循环;在带有排序的版本中,它执行(比如)哈希匹配或合并连接。
我发现 SQL 服务器在引用其他视图的复杂视图中的查询计划经常有问题,特别是如果那些 sub-views 有组 bys/sorts/etc.
为了演示可能发生的差异,我会将您的复杂视图简化为 2 个子组,我将调用 'view groups'(可能是一个或多个视图和 tables - 不是一个技术术语,只是一个总结它们的术语。
- 第一个视图组包含最多 table 个,
- 第二个视图组包含从 tables 6 和 7 获取数据的视图。
对于这两种方法,SQL 如何使用视图组中的数据可能是相同的(例如,使用相同的索引等)。但是,它在两个视图组之间进行连接的方法有所不同。
示例 - 查询规划器低估了视图组 2 的成本并且不关心使用哪种方法
我猜
- 第一个视图组在连接点处理大约 3000 行(它还没有过滤下来),并且
- 查询生成器认为视图组 2 很容易 运行
在没有 order by 的版本中,查询计划设计有nested loop
连接。也就是说,它获取视图组 1 中的每个值,然后针对每个值,它 运行 在视图组 2 中获取相关数据。这意味着视图组 2 是 运行 3000 次(视图组 1 中的每个值一次)。
在版本中,它决定在视图组1和视图组2之间进行(比方说)哈希匹配。这意味着它只需要运行 查看组 2 一次,但要花更多时间对其进行排序。但是,因为您要求对它进行排序,所以它选择了哈希匹配。
但是,由于设计的查询低估了视图组 2 的成本,事实证明哈希匹配是一种更好的查询计划。
示例 - 查询计划器使用缓存计划
我相信(但可能是错误的!)当您在视图中引用视图时,它通常可以只使用 sub-views 的缓存计划,而不是尝试为您的当前情况获得最佳计划。
可能是您的一个视图使用了“缓存计划”,而另一个试图优化查询计划,包括 sub-views。
具有讽刺意味的是,查询版本 with order by 可能更复杂,在这种情况下它使用视图组 2 的缓存计划。但是,因为它知道它没有优化视图组 2 的计划,它只是为视图组 2 获取一次数据,然后将所有结果保存在内存中并在哈希匹配中使用它。
相比之下,在没有 order by 的版本中,它尝试优化查询计划(包括优化它使用视图的方式),并把它弄得一团糟。
可能的解决方案
这些都是可能性 - 它们可能使情况变得更好,也可能使情况变得更糟!请注意,SQL 是一种声明性语言(您告诉计算机 do/what 您想要什么,而不是如何去做)。
这不是一份完整的可能性列表,但您可以尝试这些方法
- Pre-calculate 全部或部分视图(例如,将 tables 6 和 7 中的 pre-calculated 内容放入临时 table,然后在视图中使用临时 tables)
- 简化 SQL and/or 将所有 SQL 移动到不调用其他视图的单个视图中
- 使用连接提示,例如,在适当的位置使用 INNER HASH JOIN 而不是 INNER JOIN
- 使用选项(重新编译)