CTE,子查询,临时表或表变量之间是否存在性能差异?

时间:2012-06-23 12:36:14

标签: sql sql-server tsql subquery common-table-expression

在这个出色的SO question中,讨论了CTEsub-queries之间的差异。

我想特别问:

在什么情况下,以下各项更有效/更快?

  • CTE
  • 子查询
  • 临时表
  • 表格变量

传统上,我在开发temp tables时使用了很多stored procedures - 因为它们看起来比许多交织在一起的子查询更具可读性。

Non-recursive CTE非常好地封装数据集,并且非常易读,但是在某些特定情况下可以说它们总能表现得更好吗?或者是不得不总是用不同的方法来找到最有效的解决方案呢?


编辑

我最近被告知,就效率而言,临时表是一个很好的首选,因为它们有相关的直方图,即统计数据。

4 个答案:

答案 0 :(得分:197)

SQL是一种声明性语言,而不是一种过程语言。也就是说,您构造一个SQL语句来描述所需的结果。您没有告诉SQL引擎如何来完成工作。

作为一般规则,让SQL引擎和SQL优化器找到最佳查询计划是个好主意。开发SQL引擎需要花费很多人一年的时间,所以让工程师做他们知道怎么做的事。

当然,有些情况下查询计划不是最佳的。然后,您希望使用查询提示,重组查询,更新统计信息,使用临时表,添加索引等,以获得更好的性能。

至于你的问题。理论上,CTE和子查询的性能应该相同,因为它们都向查询优化器提供相同的信息。一个不同之处在于,使用一次以上的CTE可以很容易地识别和计算一次。然后可以多次存储和读取结果。不幸的是,SQL Server似乎没有利用这种基本的优化方法(你可以称之为常见的子查询消除)。

临时表是另一回事,因为您提供了有关如何运行查询的更多指导。一个主要区别是优化器可以使用临时表中的统计信息来建立其查询计划。这可以带来性能提升。此外,如果您有一个多次使用的复杂CTE(子查询),那么将其存储在临时表中通常会提高性能。查询只执行一次。

您的问题的答案是您需要四处寻找您期望的性能,特别是对于定期运行的复杂查询。在理想的世界中,查询优化器将找到完美的执行路径。虽然经常这样做,但您可以找到一种方法来获得更好的性能。

答案 1 :(得分:64)

没有规则。我发现CTE更具可读性,并使用它们除非它们表现出一些性能问题,在这种情况下我调查实际问题而不是猜测CTE是问题并尝试使用不同的方法重新编写它做法。问题通常比我选择以声明方式陈述我对查询的意图的方式更多。

当然,您可以解开CTE或删除子查询并将其替换为#temp表并减少持续时间。这可能是由于各种各样的事情,例如陈旧的统计数据,甚至无法获得准确的统计数据(例如加入表值函数),并行性,甚至由于查询的复杂性而无法生成最优计划(在这种情况下,打破它可能会给优化者一个战斗机会)。但也有一些情况,创建#temp表所涉及的I / O可能超过其他性能方面,这可能会使使用CTE的特定计划形状变得不那么有吸引力。

老实说,有太多的变量可以为你的问题提供“正确”的答案。没有可预测的方法可以知道查询何时可能倾向于支持一种或另一种方法 - 只要知道理论上CTE或单个子查询的相同语义应执行完全相同的语义。如果你提出一些不正确的情况,我认为你的问题会更有价值 - 可能是你在优化器中发现了一个限制(或发现了一个已知的限制),或者你的查询可能在语义上不等同或者那个包含阻碍优化的元素。

因此,我建议以对您来说最自然的方式编写查询,并且只有在您发现优化程序遇到的实际性能问题时才会出现偏差。就个人而言,我将它们排在CTE,然后是子查询,而#temp表是最后的选择。

答案 2 :(得分:16)

#temp是materalized而CTE不是。

CTE只是语法,所以理论上它只是一个子查询。它被执行了。 #temp已实现。因此,在#temp中,执行多次的连接中的昂贵CTE可能更好。另一方面,如果这是一个简单的评估,但没有执行,但有几次不值得#temp的开销。

有些人在SO上不喜欢表变量,但我喜欢它们,因为它们是物化的,比#temp更快。有时候,查询优化器使用#temp与表变量相比做得更好。

在#temp或table变量上创建PK的能力为查询优化器提供了比CTE更多的信息(因为您无法在CTE上声明PK)。

答案 3 :(得分:11)

我认为只需要2件事情就可以使用#Temp Table而不是CTE了:

  1. 您不能将主键放在CTE上,因此CTE访问的数据必须遍历CTE表中的每个索引,而不是只访问临时表上的PK或索引。

  2. 因为您无法向CTE添加约束,索引和主键,所以它们更容易出现漏洞和错误数据。

  3. 昨天晚上

    这是一个示例,其中#table约束可以防止坏数据,而CTE的情况并非如此

    DECLARE @BadData TABLE ( 
                           ThisID int
                         , ThatID int );
    INSERT INTO @BadData
           ( ThisID
           , ThatID
           ) 
    VALUES
           ( 1, 1 ),
           ( 1, 2 ),
           ( 2, 2 ),
           ( 1, 1 );
    
    IF OBJECT_ID('tempdb..#This') IS NOT NULL
        DROP TABLE #This;
    CREATE TABLE #This ( 
                 ThisID int NOT NULL
               , ThatID int NOT NULL
                            UNIQUE(ThisID, ThatID) );
    INSERT INTO #This
    SELECT * FROM @BadData;
    WITH This_CTE
         AS (SELECT *
               FROM @BadData)
         SELECT *
           FROM This_CTE;