265

在这个优秀的SO questionCTE中,讨论了和之间sub-queries的差异。

我想具体问一下:

在什么情况下,以下每种情况更有效/更快?

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

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

Non-recursive CTEs 很好地封装了数据集,并且可读性很好,但是在特定情况下可以说它们总是会表现得更好吗?还是必须总是摆弄不同的选项才能找到最有效的解决方案?


编辑

最近有人告诉我,就效率而言,临时表是一个不错的首选,因为它们具有相关的直方图,即统计信息。

4

4 回答 4

293

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

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

当然,也有查询计划不是最优的情况。然后你想使用查询提示、重组查询、更新统计信息、使用临时表、添加索引等等以获得更好的性能。

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

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

您的问题的答案是您需要尝试获得您期望的性能,特别是对于定期运行的复杂查询。在理想情况下,查询优化器会找到完美的执行路径。尽管它经常这样做,但您也许能够找到一种获得更好性能的方法。

于 2012-06-23T13:32:05.577 回答
92

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

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

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

因此,我建议以对您来说最自然的方式编写查询,并且仅在您发现优化器遇到的实际性能问题时才会偏离。我个人对它们进行 CTE 排名,然后是子查询,#temp 表是最后的手段。

于 2012-06-23T15:55:53.093 回答
24

#temp 是物质化的,而 CTE 不是。

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

SO上有一些人不喜欢表变量,但我喜欢它们,因为它们比#temp 更具体化并且创建速度更快。与表变量相比,查询优化器有时使用#temp 做得更好。

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

于 2012-06-25T01:03:15.573 回答
13

我认为只有两件事让使用# Temp Table 而不是 CTE 总是更可取的是:

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

  2. 因为您不能向 CTE 添加约束、索引和主键,所以它们更容易出现错误和不良数据。


-onedaywhen昨天

这是一个示例,其中#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;
于 2016-06-27T09:50:43.393 回答