繁体   English   中英

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

[英]Is there a performance difference between CTE , Sub-Query, Temporary Table or Table Variable?

在这个优秀的SO 问题中,讨论了CTEsub-queries差异。

我特别想问:

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

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

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

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


编辑

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

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

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

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

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

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

您的问题的答案是,您需要尝试获得预期的性能,尤其是对于定期运行的复杂查询。 在理想的世界中,查询优化器会找到完美的执行路径。 虽然它经常这样做,但您也许能够找到一种方法来获得更好的性能。

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

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

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

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

#temp 是材料化的,而 CTE 不是。

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

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

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

我认为只有两件事使使用 # Temp Table 而不是 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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM