为什么这个(非相关)子查询会导致这样的问题?

时间:2010-08-26 17:19:21

标签: sql sql-server sql-server-2005 tsql subquery

我有一个大型查询,其中一个简单的子查询优化将它从8分钟降低到20秒。我不确定我理解为什么优化会产生如此巨大的影响。

从本质上讲,这是问题部分:

SELECT  (bunch of stuff)
FROM
  a LEFT OUTER JOIN b ON a.ID = b.a
  LEFT OUTER JOIN c ON b.ID = c.b
  ...
  ...
      INNER JOIN veryLargeTable 
      ON a.ID = veryLargeTable.a 
         AND veryLargeTable.PetID = 
             (SELECT id from Pets WHERE Pets.Name = 'Something')    /* BAD! */
  ...
  ...

总共有16个连接表。如果我用包含petID的预填充变量(而不是使用子查询)替换veryLargeTable连接的第二个谓词,则整个查询会显着加快

AND veryLargeTable.PetID = @petID   /* Awesome! */


显然,(SELECT id from Pets WHERE Name = 'Something')正在为每一行执行。有两件事我不太了解:

  1. 据我所知,这是一个非相关的子查询。 Pets表根本不是外部查询的一部分。是不是独立评估了非相关子查询(并因此进行了优化)?为什么不是这种情况?

  2. 执行计划截然不同。在失败的情况下(上图),整个子树处理估计的950k行。在win情况下(使用变量而不是子查询),估计行数仅约为125k。这是怎么回事?如果那个子查询在那里,为什么涉及这么多行呢? Pets.Name列肯定有唯一的数据(但据我所知,没有唯一的约束)。

  3. 请注意,将谓词移动到WHERE子句不会像我期望的那样影响查询,因为它是一个内部联接。

    见解赞赏!

4 个答案:

答案 0 :(得分:4)

作为替代方案,我认为你可以用以下方法消除子查询:

...
INNER JOIN veryLargeTable vLT
    ON a.ID = vLT.a 
INNER JOIN Pets p
    ON vLT.PetID = p.id
        and p.Name = 'Something'
...

答案 1 :(得分:4)

根据我的经验,查询越复杂,SQL优化器创建灵巧计划的能力就越差。在这里你有16个连接,一些或大多数是外连接,你至少有一个子查询...折腾足够的索引,基数,视图,外部适用,谁知道还有什么,没有人,甚至微软工程师*,可以找出统一和定期生成最佳计划的例程。

你所描述的,我经历过无数次 - 在一个混乱的查询中改变一个简单的事情,一切都快一个数量级(或者,咬牙,慢一点)。我没有办法确定复杂性何时太复杂,它更像是一种感觉而不是其他任何东西。我的一般经验法则是,如果它看起来太长或太复杂,可以简化您的选择 - 例如您预先选择的单一嵌套值,或者突破部分查询,而不是总是运行快速使用小结果集,然后先运行它并将结果存储在临时表中。

(*请注意,这是温和的sarcsam)

答案 2 :(得分:0)

我个人认为如果Pets.Name上没有索引,结果就不足为奇了。如果您在Pets.Name上创建唯一索引,您可能会看到更好的结果。从服务器的角度来看,如果没有索引,子查询可能会返回多行或NULL。也许优化者可以做得更好;它经常需要帮助。

答案 3 :(得分:0)

原因正如您所指出的那样,根据我的经验,即使最简单的非相关子查询通常也会被SQL Server的查询优化器重新计算。

例如,您可以查看以下查询的执行计划,并查看是否重新计算了非相关子查询。

SELECT ID
FROM #table1
WHERE ID in (SELECT ID from #table1)
UNION ALL
SELECT ID
FROM #table1
WHERE ID in (SELECT ID from #table1)

在属性上有或没有聚集索引," ID"在这种情况下。正如有人指出的那样,您可以重写此查询以使用连接而不是子查询。但是,在许多情况下,如果子查询返回聚合标量,例如

,则可以这样做
where ID = (select MAX(ID) from #table1)

然后连接重写可能不那么容易。