为什么在 SQL Server 中“select *”比“select top 500 *”快?

use*_*867 19 performance sql-server execution-plan select top query-performance

我有一个观点,complicated_view-- 有一些连接和 where 子句。现在,

select * from complicated_view (9000 records)
Run Code Online (Sandbox Code Playgroud)

更快,更快,比

select top 500 * from complicated_view
Run Code Online (Sandbox Code Playgroud)

我们说的是 19 秒对 5+ 分钟。

第一个查询返回所有 9000 条记录。如何只获得前 500 名的时间长得可笑?

显然,我将在这里查看执行计划 ---- 但是一旦我弄清楚为什么SQL Server 以次优方式运行“前 500”,我该如何实际告诉它以快速方式运行计划,喜欢坐满桌?

当然,我可能不得不完全重写视图——但很奇怪。

基本上,我将此数据表连接到第 3 方软件,该软件使用select top 500 *无法修改的默认查询预先检查表。因此,除了将此视图转储到实际表中(非常草率)之外,我也无法绕过他们的“前 500 名”附录。

这是 SQL Server 2012。

编辑:不同意重复标志。另一个问题,顶部比所有的都快。这将是预期的行为,返回较少的行。我的情况正好相反。另外,我的理解是 Top 100 是一种与 Top 100+ 不同的算法。我什至不认为重复的问题有正确的答案。也就是说,TOP X 查询将在很早的时候对潜在的大量表进行排序,而不是在它们被聚合/过滤/等之后。为什么是一个谜,但如何显然存在。

Joe*_*ish 27

TOP查询添加子句会为查询引入行目标。查询优化器将尝试利用不需要返回所有行的事实来创建更有效的查询计划。行目标可能会导致某些运算符的成本降低。由于模型限制或统计对象中的信息不完整,行目标优化可能不利于查询调谐器。下面我有一个针对简单视图的演示,添加TOP 500会降低性能。

首先只将奇数插入表中。请注意,我在最后收集了完整的统计数据。

DROP TABLE IF EXISTS dbo.ODD;

CREATE TABLE dbo.ODD (
    ID BIGINT NOT NULL,
    FLUFF VARCHAR(10)
);

INSERT INTO dbo.ODD WITH (TABLOCK)
SELECT TOP (100000)
  -1 + 2 * ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, REPLICATE('FLUFF', 2)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

CREATE STATISTICS S ON dbo.ODD (ID) WITH FULLSCAN;
Run Code Online (Sandbox Code Playgroud)

然后只将偶数插入到不同的表中。我正在用重复的值和行大小做一些事情来使演示工作。最后我仍然完整地更新统计数据。

DROP TABLE IF EXISTS dbo.EVEN;

CREATE TABLE dbo.EVEN (
    ID BIGINT NOT NULL,
    FLUFF VARCHAR(3500)
);

INSERT INTO dbo.EVEN WITH (TABLOCK)
SELECT TOP (100000) 
  1000 * FLOOR ( ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) / 500)
, REPLICATE('FLUFF', 700)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2

CREATE STATISTICS S ON dbo.EVEN (ID) WITH FULLSCAN;
Run Code Online (Sandbox Code Playgroud)

这是视图定义:

CREATE OR ALTER VIEW dbo.TRICKY_VIEW AS
SELECT o.ID
FROM dbo.ODD o
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.EVEN e WHERE o.ID = e.ID
);
Run Code Online (Sandbox Code Playgroud)

考虑以下查询:

SELECT TOP 500 *
FROM dbo.TRICKY_VIEW
OPTION (MAXDOP 1);
Run Code Online (Sandbox Code Playgroud)

下面是查询计划的样子:

糟糕的计划

成本计算限制导致EVEN嵌套循环连接内侧的表的完整扫描具有较低的相对成本。根据我构造数据的方式,我们知道优化器需要从EVEN表中扫描 500 * 100000 = 5000 万行,以便将前 500 行返回给客户端。这确实发生了,查询在我的机器上执行大约需要 16 秒:

糟糕的实际计划

TOP从查询中删除子句提供了不同且更有效的计划

好计划

这个查询在我的机器上执行不到半秒。从EVEN表中仅读取 100000 行。

对于 SQL Server 2016 及更高版本,您可以通过添加OPTION (USE HINT('DISABLE_OPTIMIZER_ROWGOAL'))到查询来解决此问题,而无需更改视图的定义。该提示在查询级别禁用行目标优化。对于 SQL Server 2012,您可以通过在查询级别使用跟踪标志 4138 OPTION (QUERYTRACEON 4138),但这需要 SA。

在没有看到查询计划的情况下,我无法特别说明您的查询,但希望这个示例说明了一般观点。

  • 如果其他人正在阅读这篇文章......并且两者都没有访问querytraceon并且正在使用SQL Server 2014或更早版本......我还找到了另一个解决方案......如果被迫执行“选择前500 *”从一个角度来看——查询优化器是 Bozo the Clown 坏掉了......另一个解决方法是 select 500 * from (select top (2147483647) * from view) .... 如本视频所示:https://sqlbits。 com/Sessions/Event14/Query_Tuning_Mastery_Clash_of_the_Row_Goals ----- 不知何故,第二个数字(21 亿,这可能是最大参数)......让系统期待“所有”行。 (3认同)