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



以下查询需要很长时间才能完成。但是,如果我删除top 10子句,它会很快完成。big_ table_1和big_table_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
    )

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

我的第一个想法是,您正在从(select*….)中选择顶部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组合在一起的方式确实会影响对数据库执行某些步骤的顺序。

SELECT TOP速度较慢,与ORDER BY 无关

为什么在SQL Server中对索引列执行top(1)操作很慢?

我在使用类似的查询时遇到了类似的问题。已排序但不包含top子句的查询花费了1秒,而包含top 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
    )

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

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

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

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

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

在Oracle中,有一些"第一行"one_answers"所有行"提示在某种程度上与操纵这种行为有关。AskTom讨论。

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


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

使用jdbc客户端对流水线函数执行eQuery。默认情况下,Oracle语句fetchSize为10。循环并打印带有时间戳的结果。看看结果是否交错。我无法用Postgresql(RETURNNEXT)测试这一点,因为Postgres不会流式传输函数的结果。

Oracle流水线函数

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

Postgresql返回下一个

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

JDBC默认获取大小

statement.setFetchSize(100);

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

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

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

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

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

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

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的联接表的平均行数。代码约为35K。实际结果集只有3行,因为t1.Code='MyCode'只匹配t2中只有3行对应的2行。统计数据是最新的。

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

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

  • 没有TOP的计划在t1上使用索引查找。代码,找到2行,然后嵌套循环在t2上通过索引查找连接3行。速度很快
  • 带有TOP的计划在t2使用索引扫描,给出20M行,然后在t1通过索引查找将嵌套循环连接到2行。编码,然后应用TOP运算符

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

因此,至少在我的情况下,得出的结论是,这个问题是统计数据中没有反映的数据分布不均衡的结果。

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

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

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

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

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

Gísli

比较两者的执行计划可能是个好主意。你的统计数据可能已经过时了。如果你看到实际执行计划之间的差异,那就是你的性能差异。

在大多数情况下,你会期望在前十名中有更好的表现。在你的情况下,表现更差。如果是这种情况,您不仅会看到执行计划之间的差异,还会看到估计执行计划和实际执行计划中返回的行数的差异,从而导致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

我发现这种格式可读性更强,尽管它应该(尽管可能没有)优化到相同的执行计划。无论

如何,返回的最终结果都将相同

最新更新