我是 CTE 的超级粉丝。他们是最棒的。你知道,我知道,我们都知道。
今天,如果将 CTE 写入真实表,查询速度会提高大约 1000 倍,这让我很头疼。
查询本身在链接中,但我怀疑它并不重要。为简化起见,我需要一份保险单中所有索赔的清单,其中一项索赔满足特定条件 (cat = 1)。
简化版本如下所示:
with cat_claims as (select distinct pol_nbr from claims where cat = 1)
select * from claims c
inner join policies p on p.polnbr = c.polnbr
inner join cat_claims cat on c.pol_nbr= cat.pol_nbr
这是超级慢。如果我重写它看起来像这样:
with cat_claims as (select distinct claim_nbr from claims where cat = 1)
select * into cat_claims
from cat_claims
select * from claims c
inner join policies p on p.polnbr = c.polnbr
inner join cat_claims cat on c.pol_nbr = cat.pol_nbr
drop table cat_claims
它的方式,方式更快。这对我来说很奇怪。
问题为什么将 CTE 写入实际表会提高性能?
慢计划:
https://www.brentozar.com/pastetheplan/?id=HkyrpAtH4
快速计划:
尽管它们是估计的计划而不是实际的计划, 但对我
RATING_CONTRIB_LOSS
来说RATING_CONTRIB_USR
, 差异似乎来自加入表:、、、、, 以及来自评级的表。cmbgrp
rating_revision_set
Rating
CTE
加入这些表会产生巨大的表线轴(惰性线轴),在实际计划中可能会更大。
对于嵌套循环运算符顶部的每一行,都会扫描底部结果集,从而产生
第一个 3361 行 * 3359
table spool
-->Nested loops
-->Top(1)
和
第二行 1541 行 * 3359
table spool
-->Nested loops
-->Top(1)
这些表假脱机不存在于临时表查询的两个计划中。
我得到的是插入到表中的查询部分与
CTE
. 有一些差异,但我认为减速的主要原因是表线轴。差异的一个示例是计划中的全表扫描
RATING_CONTRIB_LOSS
更改CTE
为临时表计划中具有残留谓词的扫描。热膨胀系数
临时表
您可能会争辩说,使用临时表的查询的第二部分可能会
RATING_CONTRIB_LOSS
再次访问该表。而且我必须同意,因为它将进行昂贵的密钥查找。然而,计划中的第二个表假脱机
CTE
也是基于与RATING_CONTRIB_LOSS
表的嵌套循环连接,这在临时表计划中不存在,这是一个很大的优势。结论
我确实相信执行时间的差异来自于使用临时表结果的查询,而不使用昂贵的运算符。更具体地说,表线轴。我需要 100% 确定实际计划。
作为旁注,在许多情况下,将 CTE 重写到临时表或只是拆分查询通常可以提供更好的执行时间。
猜测一个装有苹果和梨的袋子里有多少个苹果比猜测一个装有苹果、梨、芒果、橙子、桃子……的袋子里有多少苹果更容易(是的,水果 = 桌子)。
我还会使用实际的#temp 表来存储你的中间结果。
比较 CTE & Temp 表插入查询部分
计划与 CTE 第 1 部分
计划与 CTE 第 2 部分
计划临时表第 1 部分
RATING_CONTRIB_LOSS 和 Hash match join <> Nested Loops Join 的区别。其余非常相似,评级表上的相同操作(2x)