为什么选择Top子句可能会导致长时间的成本

时间:2012-03-08 11:30:53

标签: sql sql-server sql-server-2008-r2

以下查询需要永远完成。但如果我删除前10名条款,它会很快完成。 big_table_1和big_table_2是2个表,有10 ^ 5条记录。

我曾经认为顶级条款会降低时间成本,但显然不在这里。为什么???

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
    )

9 个答案:

答案 0 :(得分:13)

关于同一主题还有其他stackoverflow讨论(底部的链接)。正如上面的评论中所指出的,它可能与索引和优化器混淆并使用错误的索引有关。

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

考虑将其重写为

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放在一起的方式可以真正影响在某些步骤中执行某些步骤的顺序。 DB。

SELECT TOP is slow, regardless of ORDER BY

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

答案 1 :(得分:4)

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

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

在Oracle中,有“first_rows”和“all_rows”提示与操纵此类行为有些相关。 AskTom discussion

如果内部查询在生成第一行和生成最后一行之间需要很长时间,那么这可能是发生了什么的指示。作为调查的一部分,我将采用内部查询并修改它以具有分组功能(或排序)以强制处理所有行,然后才能返回结果。我会用它来衡量内部查询与外部查询中的时间进行比较所需的时间。


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

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

Oracle Pipelined Function

  

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

Postgresql RETURN NEXT

  

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

JDBC Default Fetch Sizes

statement.setFetchSize(100);

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:2)

在调试这样的事情时,我发现找出SQL Server“看到”两个查询的最快方法是查看他们的查询计划。在查询视图中点击SSMS中的CTRL-L,结果将显示在实际执行查询时用于构建结果的逻辑。

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

答案 4 :(得分:2)

我的查询与您的查询类似。订购的查询但没有顶部子句需要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
    )

答案 5 :(得分:1)

除非您使用order by,否则TOP不会根据我的知识对结果进行排序。

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

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

为了让您的查询更快地运行,您可以在big_table_1中创建servicerequestid和statusid的索引,在big_table_2中创建customerreferencenumber的索引。如果您创建非聚簇索引,则应该获得具有非常快速结果的仅索引计划。

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

吉斯利

答案 6 :(得分:1)

如果要比较两个查询的性能,则必须在相同的情况下运行这两个查询(使用干净的内存缓冲区)并具有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 :(得分:1)

我只是需要调查一个非常类似的问题。

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运算符。

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

因此,至少在我的情况下,结论是问题是由于数据分布不均匀而未在统计数据中反映出来。

答案 8 :(得分:0)

比较两者之间的执行计划可能是个好主意。您的统计信息可能已过期。如果您发现实际执行计划之间存在差异,那么您的绩效就会有所不同。

在大多数情况下,您会期望在前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

我发现这种格式更具可读性,但它应该(尽管可能远程可能不会)优化到同一个执行计划。无论

,返回的endresult都是相同的