SQL TOP 5000比普通查询更快,结果行少于5000?

时间:2013-01-09 15:29:52

标签: sql sql-server tsql

我注意到一些奇怪的行为:

运行此查询:

SELECT TOP 5000  t1.f1,t1.f2,t1.f3 
FROM t1
JOIN t2 on t1.f1 = t2.f1
WHERE t2.f1 IS NOT NULL AND (t1.f5 != t2.f3)

在2秒内产生3447行。

运行这个:

SELECT t1.f1,t1.f2,t1.f3 
FROM t1
JOIN t2 on t1.f1 = t2.f1
WHERE t2.f1 IS NOT NULL AND (t1.f5 != t2.f3)

永远运行直到我停止它(至少120分钟!!)。

t1t2包含大约500,000条记录。

我总是认为如果总行数低于该数字,则TOP语句无关紧要,但是,似乎存在非常显着的差异。这是正常的(如果是这样,为什么)或者这只是一个侥幸?

修改

根据要求:

T1:

CREATE TABLE [dbo].[t1](
    [f1] [int] NOT NULL,
    [f2] [varchar](10) NULL,
    [f3] [varchar](4) NULL,
    [f4] [int] NOT NULL,
    [f5] [varchar](max) NULL,
 CONSTRAINT [PK_t1] PRIMARY KEY CLUSTERED 
(
    [f1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

F2:

CREATE TABLE [dbo].[t2](
    [f1] [nchar](10) NOT NULL,
    [f2] [nchar](10) NOT NULL,
    [f3] [varchar](max) NOT NULL,
    [f4] [nchar](10) NULL,
    [f5] [date] NULL,
    [f6] [date] NULL,
    [f7] [nchar](1) NULL,
 CONSTRAINT [PK_t2] PRIMARY KEY CLUSTERED 
(
    [f1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

执行计划:

有顶: Execution with top

没有顶部: Exec w/o top

看着这个我必须得出结论sorting(为什么这样做?)导致延迟...你同意吗?

Edit2:根据要求,带有循环选项的执行计划没有顶部: enter image description here

2 个答案:

答案 0 :(得分:13)

问题是你的两个表[t1]和[t2]对于JOIN列f1有完全不同的(并且很大程度上不兼容)数据类型。

这使得查询优化器无法准确估计这两个500,000行表之间要匹配的行数。它似乎使用默认的“猜测”,在这种情况下是实际数字的总 - 估计(3477)。因此,当你不使用TOP时,它认为排序然后合并行(O(NLogN))比执行嵌套循环(O(N ^ 2))更有效,因为它确实如此。没有意识到(合并)JOIN实际上会消除几乎所有的行。

当你打开TOP 5000时,它意识到嵌套循环更好,因为它将被切断不超过5000(远小于500k ^ 2,甚至小于500k * Log(500k)) 。但与嵌套循环不同,Merge-Sort不能以递增方式完成,它必须首先包含Sort的所有行。因此,将输出切断为5000,根本不会为您节省太多,因此使嵌套循环显然是更好的选择(即使JOIN估计错误)。


这里的根本问题是列T2.f1是NCHAR(10),对于看起来应该包含整数的东西来说,这是一个非常糟糕的选择。最好的解决方案是将该列的数据类型更改为INT。

如果由于某种原因你无法做到这一点,那么根据你的SQL Server版本,你可以通过添加一个持久的计算列来计算INT转换后的值[f1],然后抛出一个兼容的数据来结束运行上的索引。这将允许索引和统计数据再次用于此类查询。

作为最后的手段,您还可以使用查询提示。我通常不推荐它们,因为它们往往是后来导致问题的权宜之计解决方案。但是,如果您认为这是您唯一的选择,那么在查询结尾添加OPTION (FAST 1000)可能会有效。

答案 1 :(得分:2)

可以通过多种不同方式优化SQL查询。两种常见方式是“最快的第一行”和“最快的最后一行”。也就是说,您是否希望最小化获得任何结果的时间或获得完整结果集的时间。

我猜这两个版本的优化程度不同。正如Aaron建议的那样,你可以通过查看执行计划来检查这一点。我通常的赌注是慢速版本使用嵌套循环连接。您可以使用优化程序提示解决此问题,例如:

<your query>
option (MERGE JOIN, HASH JOIN)

还有其他可能性。也许这些表正在更新,并且在运行第二个查询时,表恰好具有完整的表锁。您可以使用sp_who2

进行检查