为啥 select Top 子句会导致长时间的成本

Posted

技术标签:

【中文标题】为啥 select Top 子句会导致长时间的成本【英文标题】:Why select Top clause could lead to long time cost为什么 select Top 子句会导致长时间的成本 【发布时间】:2012-03-08 11:30:52 【问题描述】:

以下查询需要很长时间才能完成。但是如果我删除 top 10 子句,它会很快完成。 big_table_1 和 big_table_2 是 2 个表,有 10^5 条记录。

我曾经相信top从句会降低时间成本,但这里显然没有。为什么???

select top 10 ServiceRequestID
from 
(
    (select * 
     from  big_table_1
     where big_table_1.StatusId=2
    ) cap1
    inner join
      big_table_2 cap2
    on cap1.ServiceRequestID = cap2.CustomerReferenceNumber
    )

【问题讨论】:

也许TOP需要对结果进行排序。 发布有关此表上的索引的信息 索引应该对此有影响吗?我认为没有排序。 @smwikipedia 请注意,我将您的嵌套选择重写为单个连接,这样可以加快速度。 请:SQL ServerSQL 不是一回事。为了使您的问题和人们的答案对其他人更有用,请指定您正在谈论的 DBMS。 【参考方案1】:

还有其他关于同一主题的 *** 讨论(底部的链接)。正如上面的 cmets 所述,它可能与索引有关,并且优化器会混淆并使用错误的索引。

我的第一个想法是您正在执行 select top serviceid from (select *....) 并且优化器可能难以将查询推送到内部查询并使用索引。

考虑重写为

select top 10 ServiceRequestID  
from  big_table_1
inner join big_table_2 cap2
on cap1.servicerequestid = cap2.customerreferencenumber
and big_table_1.statusid = 2

在您的查询中,数据库可能会尝试合并结果并返回它们,然后将其限制在外部查询中的前 10 位。在上述查询中,数据库只需要在结果合并时收集前 10 个结果,从而节省大量时间。并且如果 servicerequestID 被索引,它肯定会使用它。在您的示例中,查询正在查找已以虚拟、未索引格式返回的结果集中的 servicerequestid 列。

希望这是有道理的。虽然假设优化器应该采用我们放入 SQL 的任何格式并找出每次返回值的最佳方式,但事实是,我们将 SQL 放在一起的方式确实会影响在数据库。

SELECT TOP is slow, regardless of ORDER BY

Why is doing a top(1) on an indexed column in SQL Server slow?

【讨论】:

如果 A 列的索引为 IX_COL_A,则将以下内容添加到查询中可能会提高速度: Select * from MYTABLE WITH (NOLOCK, index(IX_COL_A)) ... WHERE ... 【参考方案2】:

我对像您这样的查询也有类似的问题。排序但没有 top 子句的查询需要 1 秒,具有前 3 个的相同查询需要 1 分钟。

我看到使用变量作为顶部它按预期工作。

您的案例代码:

declare @top int = 10;

select top (@top) ServiceRequestID
from 
(
    (select * 
     from  big_table_1
     where big_table_1.StatusId=2
    ) cap1
    inner join
      big_table_2 cap2
    on cap1.ServiceRequestID = cap2.CustomerReferenceNumber
    )

【讨论】:

是的。你说的对。这是一个可怕的错误,声明变量有效,但这根本没有任何意义。 这行得通,我一直在努力想弄清楚为什么 SELECT TOP 6311 花费了 【参考方案3】:

我无法解释原因,但我可以给出一个想法:

尝试在查询前添加SET ROWCOUNT 10。在某些情况下它帮助了我。请记住,这是一个范围设置,因此您必须在运行查询后将其设置回原来的值。

说明: SET ROWCOUNT:使 SQL Server 在返回指定的行数后停止处理查询。

【讨论】:

【参考方案4】:

这也取决于您所说的“完成”是什么意思。如果“完成”意味着您开始在 gui 上看到一些显示,那并不一定意味着查询已完成执行。这可能意味着结果开始流入,而不是流完成。当您将其包装到子查询中时,在内部查询的所有结果都可用之前,外部查询无法真正进行处理:

外部查询取决于在“完成”之前返回内部查询的最后行所需的时间 独立运行内部查询可能只需要等到返回 first 行后才能看到任何结果

在 Oracle 中,有“first_rows”和“all_rows”提示与操纵这种行为有些相关。 AskTomdiscussion.

如果内部查询在生成第一行和生成最后一行之间需要很长时间,那么这可能是正在发生的事情的一个指标。作为调查的一部分,我将采用内部查询并将其修改为具有分组函数(或排序)以强制处理所有行,然后才能返回结果。我将使用它来衡量内部查询实际花费的时间与外部查询所需的时间进行比较。


有点偏离主题,尝试在 Oracle 中模拟这样的事情可能会很有趣:创建一个流水线函数来流回数字;流回一些(比如 15 个),然后旋转一段时间,然后再流回更多。

使用 jdbc 客户端对流水线函数执行查询。 Oracle 语句 fetchSize 默认为 10。循环并打印带有时间戳的结果。看看结果是否错综复杂。我无法使用 Postgresql (RETURN NEXT) 对此进行测试,因为 Postgres 不会从函数中流式传输结果。

Oracle Pipelined Function

流水线表函数立即将一行返回给它的调用者 在处理该行并继续处理行之后。响应时间 改进,因为不需要构建整个集合,并且 在查询可以返回单个结果之前返回到服务器 排。 (此外,该函数需要更少的内存,因为对象缓存 不需要实现整个集合。)

Postgresql RETURN NEXT

注意:当前 RETURN NEXT 和 RETURN QUERY 的实现 在从函数返回之前存储整个结果集,如 上面讨论过。这意味着如果一个 PL/pgSQL 函数产生一个 非常大的结果集,性能可能很差:数据将被写入 到磁盘以避免内存耗尽,但函数本身不会 返回,直到生成整个结果集。一个未来 PL/pgSQL 版本可能允许用户定义集合返回 没有此限制的函数。

JDBC Default Fetch Sizes

statement.setFetchSize(100);

【讨论】:

当请求者专门谈论 sql server 时,oracle 处理“where rownum @sql_mommy 谢谢。我之前不确定 rdbms 类型。但我记得几年前在 Oracle 中对这种情况感到困惑。只需用“select count(*) from ()”包装内部查询就可以指示内部查询所用的真实时间长度。如果速度快,那这个想法就可以放弃了。【参考方案5】:

在调试这样的事情时,我发现弄清楚 SQL Server 如何“看到”这两个查询的最快方法是查看它们的查询计划。在查询视图的 SSMS 中点击CTRL-L,结果将显示在实际执行查询时它将使用什么逻辑来构建您的结果。

SQL Server 维护有关您的表的数据的统计信息,例如具有特定范围内数据的行数的直方图。它收集并使用这些统计信息来尝试预测针对这些表运行查询的“最佳”方式。例如,它可能有数据表明,对于某些输入,特定的子查询可能会返回 1M 行,而对于其他输入,相同的子查询可能会返回 1000 行。这可能导致它选择不同的策略来构建结果,例如使用表扫描(彻底搜索表)而不是索引查找(直接跳转到所需数据)。如果统计数据不能充分代表数据,则可以选择“错误”的策略,结果与您所经历的相似。我不知道这是否是这里的问题,但这是我会寻找的那种东西。

【讨论】:

【参考方案6】:

如果你想比较你的两个查询的性能,你必须在相同的情况下运行这两个查询(使用干净的内存缓冲区)并且有 mumeric 统计信息

为每个查询运行此批处理以比较执行时间和统计结果 (不要在生产环境中运行它):

DBCC FREEPROCCACHE
GO

CHECKPOINT 
GO

DBCC DROPCLEANBUFFERS 
GO

SET STATISTICS IO ON
GO

SET STATISTICS TIME ON
GO

-- your query here
GO

SET STATISTICS TIME OFF
GO

SET STATISTICS IO OFF
GO

【讨论】:

【参考方案7】:

我刚刚不得不调查一个非常相似的问题。

SELECT TOP 5 *
FROM t1 JOIN t2 ON t2.t1id = t1.id 
WHERE t1.Code = 'MyCode' 
ORDER BY t2.id DESC

t1 有 100K 行,t2 有 20M 行,t1.Code 的连接表的平均行数约为 35K。实际结果集只有 3 行,因为 t1.Code = 'MyCode' 仅匹配 2 行,而 t2 中只有 3 个对应行。统计数据是最新的。

使用上面的 TOP 5 查询需要几分钟,删除 TOP 5 后查询会立即返回。

有TOP和没有TOP的计划完全不同。

没有 TOP 的计划在 t1.Code 上使用索引查找,找到 2 行,然后嵌套循环通过 t2 上的索引查找连接 3 行。非常快。 带有 TOP 的计划在 t2 上使用索引扫描提供 20M 行,然后嵌套循环通过 t1.Code 上的索引查找连接 2 行,然后应用顶部运算符。

我认为让我的 TOP 计划如此糟糕的是,从 t1 和 t2 中挑选的行是一些最新的行(t1.id 和 t2.id 的最大值)。查询优化器假设从均匀分布的平均结果集中挑选前 5 行将比非 TOP 方法更快。我通过使用最早行中的 t1.code 测试了这个理论,并且使用相同的计划响应是亚秒级的。

因此,至少在我的情况下,结论是问题是由于数据分布不均匀造成的,而这并没有反映在统计数据中。

【讨论】:

【参考方案8】:

据我所知,TOP 不会对结果进行排序,除非您使用 order by。

所以我的猜测是,正如有人已经建议的那样,查询不会花费更长的时间来执行。当查询中没有 TOP 时,您只需开始更快地看到结果。

尝试使用@sql_mommy 查询,但请确保您具有以下内容:

为了让您的查询运行得更快,您可以在 big_table_1 中的 servicerequestid 和 statusid 上创建一个索引,并在 big_table_2 中的 customerreferencenumber 上创建一个索引。如果您创建非聚集索引,您应该获得一个结果非常快的仅索引计划。

如果我没记错的话,TOP 结果会和你我们在 big_table_1 上的索引的顺序相同,但我不确定。

吉斯利

【讨论】:

true - 除非您使用 order by,否则 Top 不会排序。这会减慢结果的速度,因为它会返回所有内容,排序,然后抓取前 n 个。没有 order by 的 top n 可以非常快,因为它开始合并数据并在 n 处停止以返回数据。另外,希望我能再次投赞成票,因为仅索引计划总是最好的!【参考方案9】:

比较两者之间的执行计划可能是个好主意。您的统计数据可能已过时。如果您发现实际执行计划之间存在差异,则说明您的性能存在差异。

在大多数情况下,您希望前 10 名的性能更好。在您的情况下,性能更差。如果是这种情况,您不仅会看到执行计划之间的差异,而且您还会看到估计的执行计划和实际执行计划中返回的行数存在差异,从而导致 SQL 引擎的决策不佳.

重新计算您的统计信息后重试(并在此过程中重建索引)

同时检查是否有助于取出 where big_table_1.StatusId=2 并改为使用

select top 10 ServiceRequestID
from  big_table_1 as cap1 INNER JOIN
big_table_2 as cap2
ON cap1.ServiceRequestID = cap2.CustomerReferenceNumber
WHERE cap1.StatusId=2

我发现这种格式更具可读性,尽管它应该(尽管可能不会)优化到相同的执行计划。无论如何,返回的最终结果都是相同的

【讨论】:

以上是关于为啥 select Top 子句会导致长时间的成本的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server中TOP子句可能导致的问题以及解决办法

winform批量更新数据_长时间的执行会导致界面卡死

SQL语句中,为啥where子句不能使用列别名,而order by却可以?

为啥聚集函数不能出现在where子句中

为啥加载图片很慢?

为啥“WITH”子句在 Informix 上会出现语法错误?