14

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

运行此查询:

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保存大约 500k 条记录。

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]

执行计划:

带顶: 顶部执行

没有顶部: Exec w/o top

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

Edit2:根据要求,带有循环选项的执行计划没有顶部: 在此处输入图像描述

4

2 回答 2

13

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

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

当您打开 TOP 5000 时,它会意识到嵌套循环更好,因为它会在不超过 5000 时被切断(远小于 500k^2,甚至小于 500k * Log(500k) )。但与嵌套循环不同,合并排序不能增量完成,它必须首先拥有所有行进行排序。因此,在 5000 处切断输出,根本不会为您节省太多,因此嵌套循环显然是更好的选择(即使 JOIN 估计不好)。


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

如果由于某种原因你不能这样做,那么根据你的 SQL Server 版本,你可以通过添加一个计算 [f1] 的 INT 转换值的持久计算列来结束运行它,然后在其上抛出一个兼容索引. 这将允许索引和统计再次为这样的查询工作。

作为最后的手段,您还可以使用查询提示。我通常不推荐它们,因为它们往往是权宜之计,以后会引起问题。但是,如果您觉得这是您唯一的选择,那么添加OPTION (FAST 1000)到查询的末尾可能会起作用。

于 2013-01-10T15:03:55.207 回答
2

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

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

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

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

于 2013-01-09T15:39:24.570 回答