CTE,子查询,临时表或表variables之间是否存在性能差异?
在这个出色的SO问题中 ,讨论了CTE和子查询之间的差异。
我想具体问一下:以下哪种情况下更有效/更快?
- CTE
- 子查询
- 临时表
- 表variables
传统上,我在开发存储过程中使用了很多临时表 – 因为它们看起来比许多交织在一起的子查询更具可读性。
非recursionCTE非常好地封装了一组数据,并且非常可读,但是在某些特定情况下,可以说它们总是会performance得更好? 还是不得不总是摆弄不同的select来find最有效的解决scheme?
编辑
我最近被告知,在效率方面,临时表是一个很好的首选,因为他们有一个相关的直方图,即统计。
SQL是一种声明性语言,而不是过程语言。 也就是说,您将构造一个SQL语句来描述所需的结果。 你并没有告诉SQL引擎如何完成这项工作。
通常,让SQL引擎和SQL优化器find最佳查询计划是一个好主意。 开发一个SQL引擎需要花费许多人力,所以让工程师们做他们知道该怎么做的。
当然,在某些情况下查询计划不是最优的。 然后,您想要使用查询提示,重新构造查询,更新统计信息,使用临时表,添加索引等以获得更好的性能。
至于你的问题。 CTE和子查询的性能在理论上应该是相同的,因为两者都向查询优化器提供相同的信息。 一个不同之处在于,不止一次使用的CTE可以容易地识别和计算一次。 结果可以被存储和读取多次。 不幸的是,SQL Server似乎没有利用这个基本的优化方法(你可以把这个常见的子查询排除掉)。
临时表是一个不同的问题,因为您正在提供关于如何运行查询的更多指导。 一个主要区别是优化器可以使用临时表中的统计信息来build立其查询计划。 这可能会导致性能提升。 此外,如果你有一个复杂的CTE(子查询),使用多次,然后将其存储在临时表中往往会提高性能。 查询只执行一次。
你的问题的答案是,你需要玩弄,以获得你期望的性能,特别是对于定期运行的复杂查询。 在理想的世界中,查询优化器将find完美的执行path。 虽然它经常这样做,但你也许可以find一个获得更好性能的方法。
没有规则。 我发现CTE更具可读性, 除非它们performance出一些性能问题, 否则我将使用它们。在这种情况下,我将调查实际问题,而不是猜测CTE是问题,并尝试用不同的方法重新编写它。 这个问题通常比我select用查询陈述我的意图的方式更多。
当然有些情况下,你可以拆开CTE或删除子查询,并用#temp表replace它们并减less持续时间。 这可能是由于各种原因,比如陈旧的统计信息,甚至无法获得准确的统计信息(如join表值函数),并行性,甚至由于查询的复杂性而无法生成最优计划在这种情况下分解可能会给优化器一个战斗机会)。 但也有一些情况下创build一个#temp表涉及的I / O可能会超过其他性能方面,可能使一个特定的计划形状使用CTE吸引力不大。
说实话,有太多的变数来为你的问题提供“正确的”答案。 有没有可预知的方式来知道什么时候查询可能倾向于一种方法或另一个 – 只是知道,在理论上,一个CTE或一个子查询相同的语义应该执行完全相同。 我想你的问题会更有价值,如果你提出一些情况,这是不正确的 – 这可能是你已经发现优化(或发现一个已知的)的限制,或者可能是你的查询不是语义等价或者一个包含阻碍优化的元素。
所以我build议用最自然的方式编写查询语句,只有在发现优化程序遇到的实际性能问题时才会出现偏差。 就我个人而言,我将它们排列为CTE,然后是子查询,#temp表是最后的手段。
#temp是input的而CTE不是。
CTE只是语法,理论上它只是一个子查询。 它被执行。 #temp被物化。 因此,一个执行多次的连接中的一个昂贵的CTE在#temp中可能会更好。 另一方面,如果这是一个简单的评估,而不是执行,但几次,那么不值得#temp的开销。
有些人不喜欢表variables,但是我喜欢他们,因为他们的创build比#temp更快。 有时候查询优化器比使用表variables的时候更好一些。
在#temp或tablevariables上创buildPK的能力使得查询优化器比CTE更多的信息(因为你不能在CTE上声明PK)。
只有2件事我认为总是最好使用一个#温度表而不是CTE:
-
您不能在CTE上放置主键,因此CTE访问的数据将不得不遍历CTE表中的每个索引,而不是仅仅访问临时表上的PK或索引。
-
因为您不能将约束,索引和主键添加到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;