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

why*_*heq 200 sql t-sql sql-server subquery common-table-expression

在这个优秀的SO问题中,讨论了CTE和之间的sub-queries差异.

我想具体问一下:

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

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

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

Non-recursive CTEs非常好地封装数据集,并且非常易读,但是在某些情况下可以说它们总能表现得更好吗?或者是否必须总是摆弄不同的选项才能找到最有效的解决方案?


编辑

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

Gor*_*off 222

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

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

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

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

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

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

  • 有关此领域可能的未来改进的一些Microsoft Research在出版物"用于查询处理的类似Subexpressions的高效利用"[可从此处获得](http://research.microsoft.com/en-us/um/people/jrzhou/ ) (10认同)
  • 鉴于该论文是在2007年提出的,任何想法是否已将其纳入SQL Server 2012? (3认同)
  • 一个很好的答案!只是要强调:SQL是一种声明性语言,我们不控制数据是如何被拉动的.因此,性能/速度因查询而异. (3认同)
  • @RGS...临时表上的索引肯定会改进可以利用这些索引的查询 - 就像永久表上的索引一样.但是,如果将子查询实现为临时表,则可能会失去原始表上索引的优势. (2认同)
  • @RGS...当数据库引擎在执行复杂查询的过程中实现子查询/ CTE时,它不会在实现时添加索引.您可以使用临时表手动执行此操作. (2认同)
  • “不幸的是,SQL Server 似乎没有利用这种基本的优化方法”——你知道 2020 年是否仍然如此? (2认同)

Aar*_*and 73

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

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

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

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

  • +1结果是一个非常主观的问题; 我希望它不会因为太过模糊而被关闭,因为迄今为止的答案都是提供信息的.我意识到:-)当问题发生变化时你不喜欢它,但你有什么建议可以缩小OP中的问题吗? (4认同)
  • 我认为这个问题很好,你会注意到还没有一个投票结束,但如果答案开始疯狂,它可能会被关闭.正如我在回答中所建议的那样,如果你有一个特殊的*情况,你看到CTE和子查询之间有很大的不同,请用实际的查询和执行计划开始一个新的问题(它可能更适合[dba] .SE](http://dba.stackexchange.com/)).只是意识到帮助*that*查询的答案对于具有相同场景的不同查询可能不是相同的答案. (2认同)
  • 上面的固定链接:https://www.sqlskills.com/blogs/bobb/does-everybody-get-that-generalizing-esoteric-optimization-techniques/ (2认同)

pap*_*zzo 17

#temp是materalized而CTE不是.

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

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

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

  • 警告 - TVP 没有执行计划!不要将 TVP 用于除最简单的短查找列表之外的任何内容。如果您对它们进行任何复杂的连接、插入或更新,您可能会遇到大量的优化问题。相信我,我已经被这件事烧焦了。 (5认同)
  • 表值参数http://msdn.microsoft.com/en-us/library/bb510489.aspx (4认同)

小智 11

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

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

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


- 昨天

下面是一个示例,其中#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;
Run Code Online (Sandbox Code Playgroud)

  • "总是"有点太远,但感谢您的回答.就可读性而言,使用CTE可能是一件好事. (3认同)
  • 我根本不明白你的第二点.我看到它的方式,定义CTE的查询类似于你对临时表的约束,注意前者可以包含任意复杂的谓词,而后者则更加有限(例如`CHECK`约束指的是多行/表是不允许的).你能发布一个例子,其中CTE表现出临时表不等同的错误吗? (3认同)