【问题标题】:SQL massive performance difference using SELECT TOP x even when x is much higher than selected rows即使 x 远高于选定的行,使用 SELECT TOP x 的 SQL 也存在巨大的性能差异
【发布时间】:2010-11-26 11:34:59
【问题描述】:

我正在从表值函数中选择一些行,但通过将 SELECT TOP 放入查询中发现了莫名其妙的巨大性能差异。

SELECT   col1, col2, col3 etc
FROM     dbo.some_table_function
WHERE    col1 = @parameter
--ORDER BY col1

需要 5 到 6 分钟才能完成。

然而

SELECT   TOP 6000 col1, col2, col3 etc
FROM     dbo.some_table_function
WHERE    col1 = @parameter
--ORDER BY col1

在大约 4 或 5 秒内完成。

如果返回的数据集很大,这不会让我感到惊讶,但所涉及的特定查询返回 200,000 行中的约 5000 行

因此,在这两种情况下,都会处理整个表,因为 SQL Server 将继续搜索它永远无法到达的 6000 行。那为什么会有巨大的差异呢?这是否与 SQL Server 在预期结果集大小时分配空间的方式有关(TOP 6000 从而使其要求较低,更容易在内存中分配)? 有没有其他人目睹过这样的事情?

谢谢

【问题讨论】:

  • 您查看过查询计划吗?有区别吗?
  • 只是好奇,如果你说 SELECT TOP 100 PERCENT ....,性能会怎样?
  • 我猜你有一些统计数据会使查询优化器陷入困境。例如,如果优化器认为表中的行很少,它可能会决定使用表扫描而不是索引查找。为什么这不会影响我不知道的 TOP 查询,但请检查执行计划。这些向您展示了服务器的功能,这将解释为什么一个服务器很慢。它还会向您显示估计的和实际的行数。如果某些估计值有偏差,请更新统计信息并重试。 :)
  • 只是一个疯狂的猜测,但前 6000 名是告诉优化器“为这 6k 条记录节省一些内存”,而没有它可能不正确的猜测将只是一些结果使引擎重新分配内存运行。我发现了一些情况,错误的内存猜测是在 ram 上运行所有内容和尝试使用 TempDB(磁盘)之间的区别

标签: sql sql-server performance tsql user-defined-functions


【解决方案1】:

表值函数的执行时间可能是非线性的。

让我们考虑这个查询的等效函数:

SELECT  (
        SELECT  SUM(mi.value)
        FROM    mytable mi
        WHERE   mi.id <= mo.id
        )
FROM    mytable mo
ORDER BY
        mo.value

这个查询(计算正在运行的SUM)在开始时很快,在结束时很慢,因为在来自mo 的每一行上,它应该对需要倒回行源的所有前面的值求和。

每行计算SUM 所需的时间随着行数的增加而增加。

如果您使mytable 足够大(例如,100,000 行,如您的示例所示)并运行此查询,您会发现它需要相当长的时间。

但是,如果您将TOP 5000 应用于此查询,您会发现它的完成速度比整个表所需时间的1/20 快得多。

很可能,你的情况也发生了类似的事情。

要说得更明确一些,我需要看一下函数定义。

更新:

SQL Server 可以将谓词推送到函数中。

例如,我刚刚创建了这个TVF

CREATE FUNCTION fn_test()
RETURNS TABLE
AS
RETURN  (
        SELECT  *
        FROM    master
        );

这些查询:

SELECT  *
FROM    fn_test()
WHERE   name = @name

SELECT  TOP 1000 *
FROM    fn_test()
WHERE   name = @name

产生不同的执行计划(第一个使用集群扫描,第二个使用带有TOP 的索引查找)

【讨论】:

  • '在这种情况下不要害怕。我的查询的重点是,无论是否使用 TOP 子句(TOP 6000 大于结果集),都会返回 same 行。因此,这与这些行本身的计算无关。
  • @Arj: 你能发布你的函数定义吗?
  • @Quassnoi:内联 TVF 只是一个宏。
【解决方案2】:

如果 col1 有索引,则不一定要处理整个表。

SQL 优化会选择是否使用索引。也许您的“TOP”正在迫使它使用索引。

如果您使用的是 MSSQL 查询分析器(我不知道这个名字),请按 Ctrl-K。这将显示查询的执行计划,而不是执行它。我相信,将鼠标悬停在图标上会显示 IO/CPU 使用率。

我敢打赌,一个正在使用索引搜索,而另一个没有。

如果您有通用客户端: 设置 SHOWPLAN_ALL 开启; 走 选择 ...; 去

详情请参阅http://msdn.microsoft.com/en-us/library/ms187735.aspx

【讨论】:

  • 是的 - 我现在正在查看计划。虽然我已经更改了发布查询。实际上它正在执行 SELECT *。我看不出使用 TOP 会如何提示使用索引?
  • SQL 优化器将决定是否使用索引。我已经完成查询,其中 where 子句导致优化器决定进行全表扫描而不是使用索引的“临界点”。
【解决方案3】:

您可能会遇到像在此处缓存这样简单的事情 - 也许(无论出于何种原因)“TOP”查询被缓存了?使用另一个不是的索引?

无论如何,消除好奇心的最佳方法是检查两个查询的完整执行计划。您可以在 SQL 管理控制台中正确执行此操作,它会准确告诉您正在完成哪些操作以及每个操作预计需要多长时间。

所有 SQL 实现都有其独特的方式 - SQL Server 也不例外。这种“哇哇哇?!”时刻很常见。 ;^)

【讨论】:

    【解决方案4】:

    您的 TOP 没有 ORDER BY,因此它与首先设置 ROWCOUNT 6000 相同。 ORDER BY 需要首先评估所有行,而且会花费更长的时间。

    如果dbo.some_table_function 是一个值 udf 的内联表,那么它只是一个扩展的宏,因此它返回前 6000 行,没有特定的顺序。

    如果 udf 是多值的,那么它就是一个黑盒子,并且总是会在过滤之前拉入完整的数据集。我认为这不会发生。

    没有直接关系,但another SO question on TVFs

    【讨论】:

      【解决方案5】:

      我认为 Quassnois 的建议似乎很合理。通过添加 TOP 6000,您隐含地向优化器提示将返回 200,000 行中的一小部分。然后优化器使用索引查找而不是聚集索引扫描或表扫描。

      正如 Jim davis 所建议的,另一种可能的解释是缓存。这很容易通过再次运行查询来排除。尝试先运行 TOP 6000 的。

      【讨论】:

        【解决方案6】:

        我遇到了同样的问题,连接五个返回 1000 行的表的简单查询需要两分钟才能完成。当我在其中添加“TOP 10000”时,它在不到一秒的时间内完成。结果发现其中一张表上的聚集索引碎片严重。

        重建索引后,查询现在在不到一秒的时间内完成。

        【讨论】:

          猜你喜欢
          • 2011-01-03
          • 1970-01-01
          • 1970-01-01
          • 2013-04-25
          • 1970-01-01
          • 2020-10-10
          • 2013-01-09
          • 2011-12-18
          • 2019-04-21
          相关资源
          最近更新 更多