哪些是性能更高的,CTE或临时表?


Answers:


62

我会说它们是不同的概念,但说“粉笔和奶酪”并不过分。

  • 临时表适合重复使用或对一组数据执行多次处理。

  • CTE可用于递归或简单地提高可读性。
    而且,就像视图或内联表值函数一样,也可以将其视为要在主查询中扩展的宏。

  • 临时表是另一个具有范围相关规则的表

我已经在其中同时使用了procs和表变量了


12
临时表还允许有时需要索引甚至统计信息,而CTE则不需要。
CodeCowboyOrg 2014年

9
我认为这个答案并未充分说明CTE会导致糟糕的性能。我通常在dba.stackexchange上引用此答案。如果我正在查找,您的问题会排在我搜索引擎的第二位,cte vs temporary tables因此恕我直言,此答案需要强调CTE更好的缺点。链接答案的TL; DR:切勿使用CTE来提高性能。。我已经接受了CTE的缺点,因此我同意该报价。
TT。

2
@TT。有趣。我发现CTE的表现要好得多
Squ1rr3lz

197

这取决于。

首先

什么是通用表表达式?

(非递归)CTE与在SQL Server中也可用作内联表表达式的其他构造非常相似。派生表,视图和内联表值函数。请注意,尽管BOL表示CTE“可以视为临时结果集”,但这纯粹是逻辑上的描述。通常,它本身并不是非物质化的。

什么是临时表?

这是存储在tempdb数据页中的行的集合。数据页可以部分或全部驻留在内存中。另外,临时表可以被索引并具有列统计信息。

测试数据

CREATE TABLE T(A INT IDENTITY PRIMARY KEY, B INT , F CHAR(8000) NULL);

INSERT INTO T(B)
SELECT TOP (1000000)  0 + CAST(NEWID() AS BINARY(4))
FROM master..spt_values v1,
     master..spt_values v2;

例子1

WITH CTE1 AS
(
SELECT A,
       ABS(B) AS Abs_B,
       F
FROM T
)
SELECT *
FROM CTE1
WHERE A = 780

计划1

请注意,以上计划中没有提及CTE1。它只是直接访问基表,因此与

SELECT A,
       ABS(B) AS Abs_B,
       F
FROM   T
WHERE  A = 780 

通过在此处将CTE实例化为中间临时表来进行重写将产生极大的反作用。

实现的CTE定义

SELECT A,
       ABS(B) AS Abs_B,
       F
FROM T

将涉及将大约8GB的数据复制到临时表中,那么从中进行选择仍然会产生开销。

例子2

WITH CTE2
     AS (SELECT *,
                ROW_NUMBER() OVER (ORDER BY A) AS RN
         FROM   T
         WHERE  B % 100000 = 0)
SELECT *
FROM   CTE2 T1
       CROSS APPLY (SELECT TOP (1) *
                    FROM   CTE2 T2
                    WHERE  T2.A > T1.A
                    ORDER  BY T2.A) CA 

上面的示例在我的计算机上花费了大约4分钟。

1,000,000个随机生成的值中只有15行与该谓词匹配,但是昂贵的表扫描进行了16次才能找到这些谓词。

在此处输入图片说明

这对于实现中间结果将是一个很好的选择。等效的临时表重写花费了25秒。

INSERT INTO #T
SELECT *,
       ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM   T
WHERE  B % 100000 = 0

SELECT *
FROM   #T T1
       CROSS APPLY (SELECT TOP (1) *
                    FROM   #T T2
                    WHERE  T2.A > T1.A
                    ORDER  BY T2.A) CA 

有计划

将查询的一部分具体化为临时表有时可能会有用,即使只对它进行一次评估-当它允许利用实现结果的统计信息重新编译其余查询时。SQL Cat文章“ 何时分解复杂查询”中提供了这种方法的示例。

在某些情况下,SQL Server将使用假脱机来缓存中间结果(例如CTE),并避免必须重新评估该子树。(迁移的)连接项中对此进行了讨论。提供提示以强制中间实现CTE或派生表。但是,不会为此创建任何统计信息,即使假脱机行的数量与估计的数量有很大出入,也无法使进行中的执行计划动态地响应以进行响应(至少在当前版本中。自适应查询计划在未来)。


33
这是回答实际问题的唯一答案(即询问哪个性能更好而不是什么不同或哪个是您的最爱),并且可以正确回答该问题:“取决于”是正确的答案。它也是唯一有解释性数据的答案,其他几个(得票率很高)明确宣称一个比另一个没有参考或证明的效果更好。要明确,所有这些答案也是错误的。因为“取决于”
Arkaine55

2
这也是一个写得很好,参考很好的答案。认真一流。
丹·威廉姆斯,

50

CTE有其用途-当CTE中的数据较小且与递归表一样时,可读性会大大提高。但是,它的性能肯定不比表变量好,并且当处理一个非常大的表时,临时表的性能明显优于CTE。这是因为您无法在CTE上定义索引,并且当您有大量数据需要与另一个表联接时(CTE就像宏一样),您无法定义索引。如果将多个表连接在一起,每个表中都有数百万行的记录,则CTE的性能将显着低于临时表。


9
我已经从自己的经历中看到了这一点。CTE的执行速度明显变慢。
goku_da_master 2011年

7
由于未缓存结果,因此CTE的执行速度也较慢。因此,每次使用CTE时,它都会重新运行查询,计划和所有操作。
goku_da_master 2011年

1
db引擎不仅可以选择重新运行查询,还可以选择重新运行查询,作为关联的子查询,对于消费者查询的每一,如果不需要,则必须始终当心。
Mike M

临时表存储在磁盘上的SQL Server的tempdb中,但具有被索引的好处,在这种情况下,SQL优化器可以很好地用于选择查询。不确定CTE存储在哪个数据库或磁盘区域上(当CTE超出内存大小并排队进行IO分页时),但是CTE从未被海量数据优化。我有时使用了编译器选项(带有重新编译选项)来使其更快
rmehra76

33

临时表始终在磁盘上-因此,只要您的CTE可以保存在内存中,它就可能会更快(也像表变量一样)。

但是话又说回来,如果CTE(或临时表变量)的数据负载太大,它也将存储在磁盘上,因此没有太大的好处。

通常,我更喜欢CTE而不是临时表,因为它在我使用后就消失了。我不需要考虑显式删除它或任何东西。

因此,最后没有明确的答案,但就个人而言,我更喜欢CTE而不是临时表。


2
对于SQLite和PostgreSQL,临时表自动删除(通常在会话结束时)。我不了解其他DBMS。
塞拉诺

1
CTE就像一个临时视图。由于不存储AFAIK数据,因此不会将任何内容保存在内存中或存储在磁盘上。重要说明,每次使用CTE时,查询都会再次运行。
罗布

1
就个人而言,我从未见过CTE可以比Temp表提高速度。使用临时表可以使调试变得容易得多
Mark Monforti

7

因此,分配给我优化的查询是用SQL Server中的两个CTE编写的。这花了28秒。

我花了两分钟将它们转换为临时表,查询用了3秒

我在连接到的字段的临时表上添加了索引,并将其降低到2秒

工作三分钟,现在通过删除CTE,其运行速度提高了12倍。我个人不会使用CTE,因为它们也很难调试。

疯狂的事情是CTE都只使用了一次,但仍然给它们添加索引证明速度提高了50%。


6

CTE不会占用任何物理空间。这只是我们可以使用联接的结果集。

临时表是临时的。我们可以像创建普通表一样创建索引,约束,因为我们需要定义所有变量。

临时表的作用域仅在会话内。EX:打开两个SQL查询窗口

create table #temp(empid int,empname varchar)
insert into #temp 
select 101,'xxx'

select * from #temp

在第一个窗口中运行此查询,然后在第二个窗口中运行以下查询,您可以找到区别。

select * from #temp

4
>>“这只是我们可以使用联接的结果集。” ->这不准确。CTE不是“结果集”,而是内联代码。SQL Server查询引擎将CTE代码解析为查询文本的一部分,并据此建立执行计划。CTE内联的想法是使用CTE的最大优势,因为它允许服务器创建“组合执行计划”
Ronen Ariely 2015年

4

我曾经使用过这两种方法,但是在庞大的复杂过程中,总是发现临时表更好地使用并且更加有条理。CTE有其用途,但通常只包含少量数据。

例如,我创建了可在15秒内返回大量计算结果的sproc,但将该代码转换为可以在CTE中运行,并且看到它可以运行8分钟以上才能达到相同的结果。


3

晚会晚了,但是...

我工作的环境非常受限,它支持某些供应商的产品并提供“增值”服务,例如报告。由于政策和合同的限制,通常不允许我使用单独的表/数据空间和/或创建永久代码的能力(根据应用程序的不同,它会变得更好一点)。

IOW,我通常不能开发存储过程,UDF或临时表等。我几乎必须通过我的应用程序界面来做所有事情(Crystal Reports-添加/链接表,设置带有CR的where子句等)。 )。节省的一个小好处是Crystal允许我使用COMMANDS(以及SQL表达式)。通过定义SQL命令,可以完成某些通过常规添加/链接表功能无法实现的工作。我通过它使用CTE,并且“远程”获得了很好的效果。CTE还可以帮助维护报表,而无需开发代码,而是交给DBA进行编译,加密,传输,安装,然后进行多级测试。我可以通过本地界面执行CTE。

使用带有CR的CTE的缺点是,每个报告都是独立的。必须为每个报告维护每个CTE。我可以在其中执行SP和UDF的地方,可以开发可以由多个报表使用的内容,仅需要链接到SP并传递参数,就好像您在处理常规表一样。CR并不是真正擅长将参数处理到SQL命令中,因此CR / CTE方面可能缺少。在这些情况下,我通常尝试定义CTE以返回足够的数据(但不返回ALL数据),然后使用CR中的记录选择功能对其进行切片和切块。

所以...我的投票是给CTE的(直到获得我的数据空间)。


3

我发现CTE在性能方面表现出色的一个用途是,我需要将一个相对复杂的Query连接到几个表,每个表都有几百万行。

我使用CTE首先根据索引列选择子集,然后首先将这些表缩减为几千个相关行,然后将CTE加入我的主查询中。这成倍地减少了查询的运行时间。

虽然不缓存CTE的结果,并且表变量可能是一个更好的选择,但我真的只是想尝试一下并找到适合上述情况的表。


另外,我觉得,因为我只用CTE在加入我才真正执行CTE曾经在我的查询,以便缓存结果并没有这方面的一个大问题
珀切斯

1

这是一个真正的开放式问题,这取决于如何使用它以及临时表的类型(表变量或传统表)。

传统的临时表将数据存储在临时数据库中,这会降低临时表的速度。但是表变量没有。


1

我刚刚对此进行了测试-CTE和非CTE(在每个联合实例中都键入了查询)都花费了大约31秒。CTE使代码更具可读性,尽管从241行减少到130行,这非常好。另一方面,Temp表将其减少到132行,并花了5秒运行。不是开玩笑。所有这些测试都已缓存-查询之前都运行过多次。


1

根据我在SQL Server中的经验,我发现CTE胜过Temp表的情况之一

我只需要在存储过程中一次使用来自复杂查询的DataSet(〜100000)。

  • 临时表在我的过程执行缓慢的SQL上造成了开销(因为临时表是在当前过程中存在于tempdb和Persist中的真实物化表)

  • 另一方面,对于CTE,CTE仅在运行以下查询之前一直存在。因此,CTE是一种范围有限的方便的内存结构。默认情况下,CTE不使用tempdb。

在这种情况下,CTE可以真正帮助简化您的代码和胜过临时表。我曾经使用过2个CTE,例如

WITH CTE1(ID, Name, Display) 
AS (SELECT ID,Name,Display from Table1 where <Some Condition>),
CTE2(ID,Name,<col3>) AS (SELECT ID, Name,<> FROM CTE1 INNER JOIN Table2 <Some Condition>)
SELECT CTE2.ID,CTE2.<col3>
FROM CTE2
GO

1
您的答案似乎很笼统...您如何衡量“ CTE胜过Temp表”?你有时间测量吗?我认为您应该编辑答案并添加更多详细信息。
Il Vic 2015年

是的,我有时间计量和执行计划来支持我的陈述。
Amardeep Kohli 2015年

由于权限有限,无法为执行计划添加img。解决
后将
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.