如果EXISTS花费的时间比嵌入的select语句长


35

当我运行以下代码时,需要22.5分钟的时间,并且需要进行1.06亿次读取。但是,如果我自己仅运行内部select语句,则只需15秒即可完成264k次读取。附带说明,select查询不返回任何记录。

知道为什么IF EXISTS它会使它运行更长的时间并进行更多的读取吗?我也将select语句更改为do,SELECT TOP 1 [dlc].[id]并在2分钟后将其杀死。

作为临时解决方案,我将其更改为执行count(*)并将该值分配给变量@cnt。然后它做一个IF 0 <> @cnt声明。但是我认为EXISTS会更好,因为如果select语句中返回了记录,则一旦找到至少一条记录,它将停止执行扫描/查找,而count(*)将会完成整个查询。我想念什么?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END

4
为了避免出现行目标问题,另一个想法(请注意,请注意!)可以尝试使用反- IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END
亚伦·伯特兰

Answers:


32

知道为什么IF EXISTS它将使它运行更长的时间并进行更多的读取吗?我也将select语句更改为do,SELECT TOP 1 [dlc].[id]并在2分钟后将其杀死。

正如我在对这个相关问题的回答中所解释的:

TOP如何(以及为什么)影响执行计划?

使用EXISTS引入了行目标,优化程序在该目标中生成旨在快速定位第一行的执行计划。在此,假定数据是均匀分布的。例如,如果统计数据显示在100,000行中有100个预期的匹配项,则将假定它仅需读取1,000行即可找到第一个匹配项。

如果此假设有误,这将导致比预期的执行时间更长的时间。例如,如果SQL Server选择的访问方法(例如无序扫描)恰好在搜索中很晚才找到第一个匹配值,则可能导致几乎完整的扫描。另一方面,如果恰好在前几行中找到匹配的行,则性能将非常好。这是行目标的基本风险-不一致的性能。

作为临时解决方案,我将其更改为执行count(*)并将该值分配给变量

通常可以重新构造查询,以便不分配行目标。如果没有行目标,则遇到第一个匹配的行(如果写正确)时,查询仍然可以终止,但是执行计划策略可能会有所不同(并且希望更有效)。显然,count(*)将需要读取所有行,因此这不是一个完美的选择。

如果运行的是SQL Server 2008 R2或更高版本,则通常还可以使用已记录并受支持的跟踪标志4138来获得没有行目标的执行计划。也可以使用受支持的提示 来指定此标志OPTION (QUERYTRACEON 4138),但是请注意,除非与计划指南一起使用,否则它需要运行时sysadmin权限。

不幸

以上所有IF EXISTS条件均不能使用条件语句。它仅适用于常规DML。它用交替工作SELECT TOP (1),你试过配方。COUNT(*)如前所述,这可能比使用必须计数所有合格行的更好。

就是说,有多种方法可以表达此要求,使您能够避免或控制行目标,同时尽早终止搜索。最后一个例子:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;

您提供的替代示例运行了3.75分钟,执行了46m读取。因此,虽然比原始查询要快,但我认为在这种情况下,我会坚持使用@cnt = count(*)并随后评估变量。特别是由于99%的时间都在运行,因此其中没有任何内容。听起来,根据您和Rob的回答,只有当您确实期望某种结果并且该结果均匀地分布在数据中时,Exists存在才是好的。
克里斯·伍兹

3
@ChrisWoods:您说过“特别是因为运行此程序的99%的时间中将没有任何内容”。这几乎可以确保一个行的目标不是一个好主意,因为您希望通常没有行,并且必须扫描所有内容以查找没有行。如果您无法添加一些巧妙的索引,请坚持使用COUNT(*)。
Ross Presser

25

由于EXISTS只需要查找一行,因此它将使用一个行目标。有时这可能会产生不理想的计划。如果您希望这样,请使用a的结果填充变量,COUNT(*)然后测试该变量是否大于0。

因此...使用较小的行目标,它将避免阻塞操作,例如建立哈希表或对合并连接有用的排序流,因为它将确定很快就会找到某些东西,因此嵌套循环将最好找到它。除此之外,这会使整个计划变得更加糟糕。如果查找单个行很快,那么您希望使用这种方法来避免出现阻塞...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.