哪个性能更高,CTE
或者Temporary Tables
?
12 回答
这取决于。
首先
什么是公用表表达式?
(非递归)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
注意在上面的计划中没有提到 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 文章When To Break Down Complex Queries中提供了这种方法的一个示例。
在某些情况下,SQL Server 将使用假脱机来缓存中间结果,例如 CTE,并避免重新评估该子树。这在(迁移的)连接项中进行了讨论,提供提示以强制 CTE 或派生表的中间实现。但是,没有对此创建统计信息,即使假脱机行的数量与估计的有很大不同,正在进行的执行计划也无法动态适应响应(至少在当前版本中。自适应查询计划可能在未来)。
我会说它们是不同的概念,但说“粉笔和奶酪”并没有太大的不同。
临时表非常适合重复使用或对一组数据执行多次处理。
CTE 可用于递归或简单地提高可读性。
并且,像视图或内联表值函数也可以像宏一样在主查询中进行扩展临时表是另一个表,其中包含一些关于范围的规则
我已经存储了我同时使用的过程(以及表变量)
CTE 有它的用途——当 CTE 中的数据很小并且可读性得到很大改进时,就像递归表中的情况一样。但是,它的性能肯定不比表变量好,当处理非常大的表时,临时表的性能明显优于 CTE。这是因为您无法在 CTE 上定义索引,并且当您有大量数据需要与另一个表连接时(CTE 就像一个宏)。如果您要连接多个表,每个表都有数百万行记录,CTE 的性能将比临时表差得多。
临时表总是在磁盘上——所以只要你的 CTE 可以保存在内存中,它很可能会更快(也像一个表变量)。
但是话又说回来,如果您的 CTE(或临时表变量)的数据负载太大,它也会存储在磁盘上,因此没有太大的好处。
一般来说,我更喜欢 CTE 而不是临时表,因为它在我使用后就消失了。我不需要考虑明确放弃它或任何东西。
所以,最终没有明确的答案,但就个人而言,我更喜欢 CTE 而不是临时表。
所以我被分配优化的查询是用 SQL server 中的两个 CTE 编写的。花了28秒。
我花了两分钟将它们转换为临时表,查询花了 3 秒
我在它正在加入的字段上的临时表中添加了一个索引,并将其缩短到 2 秒
三分钟的工作,现在通过删除 CTE,它的运行速度提高了 12 倍。我个人不会使用 CTE,因为它们也更难调试。
疯狂的是,CTE 都只使用了一次,并且仍然在它们上放置一个索引,事实证明速度快了 50%。
我都使用过,但是在大量复杂的过程中,我总是发现临时表更易于使用且更有条理。CTE 有其用途,但通常用于小数据。
例如,我创建了在 15 秒内返回大型计算结果的存储过程,但将此代码转换为在 CTE 中运行,并且已经看到它运行超过 8 分钟才能达到相同的结果。
CTE 不会占用任何物理空间。它只是一个我们可以使用 join 的结果集。
临时表是临时的。我们可以像普通表一样创建索引和约束,为此我们需要定义所有变量。
临时表的范围仅在会话内。EX:打开两个SQL查询窗口
create table #temp(empid int,empname varchar)
insert into #temp
select 101,'xxx'
select * from #temp
在第一个窗口中运行此查询,然后在第二个窗口中运行以下查询,您可以找到不同之处。
select * from #temp
派对迟到了,但是...
我工作的环境受到高度限制,支持一些供应商产品并提供报告等“增值”服务。由于政策和合同的限制,我通常不允许拥有单独的表/数据空间和/或创建永久代码的能力[它会变得更好,取决于应用程序]。
IOW,我通常不能开发存储过程或 UDF 或临时表等。我几乎必须通过我的应用程序界面来完成所有事情(Crystal Reports - 添加/链接表,从 w/in CR 中设置 where 子句等。 )。一个小小的优点是 Crystal 允许我使用命令(以及 SQL 表达式)。一些通过常规添加/链接表功能效率不高的事情可以通过定义 SQL 命令来完成。我通过它使用 CTE,并“远程”获得了非常好的结果。CTE 还有助于报告维护,不需要开发代码、交给 DBA 编译、加密、传输、安装,然后需要多级测试。我可以通过本地界面进行 CTE。
使用带 CR 的 CTE 的缺点是,每个报告都是独立的。必须为每个报告维护每个 CTE。在我可以做 SP 和 UDF 的地方,我可以开发一些可以被多个报告使用的东西,只需要链接到 SP 并传递参数,就好像你在处理一个普通的表一样。CR 并不擅长将参数处理为 SQL 命令,因此可能缺少 CR/CTE 方面的这方面。在这些情况下,我通常会尝试定义 CTE 以返回足够的数据(但不是所有数据),然后使用 CR 中的记录选择功能对其进行切片和切块。
所以......我的投票是 CTE(直到我得到我的数据空间)。
我发现 CTE 的出色性能的一个用途是我需要将一个相对复杂的查询加入到几个表中,每个表都有几百万行。
我使用 CTE 首先选择基于索引列的子集,首先将这些表减少到每个相关的几千行,然后将 CTE 加入到我的主查询中。这成倍地减少了我的查询的运行时间。
虽然 CTE 的结果没有被缓存,并且表变量可能是一个更好的选择,但我真的只是想尝试一下,发现它们适合上述场景。
我刚刚对此进行了测试——CTE 和非 CTE(为每个联合实例输入查询)都花费了大约 31 秒。CTE 使代码更具可读性——将其从 241 行减少到 130 行,这非常好。另一方面,临时表将其减少到 132 行,并用了五秒钟来运行。不是开玩笑。所有这些测试都被缓存了——查询之前都运行了多次。
这是一个非常开放的问题,这完全取决于它的使用方式和临时表的类型(表变量或传统表)。
传统的临时表将数据存储在临时数据库中,这会降低临时表的速度;但是表变量没有。
根据我在 SQL Server 中的经验,我发现了 CTE 优于 Temp 表的场景之一
我需要在我的存储过程中使用一次来自复杂查询的 DataSet(~100000)。
临时表在我的过程执行缓慢的情况下导致 SQL 开销(因为临时表是存在于 tempdb 中的真实物化表,并且在我当前过程的生命周期内持续存在)
另一方面,对于 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