优化 CTE 以在父子层次结构的某个级别返回后代

Posted

技术标签:

【中文标题】优化 CTE 以在父子层次结构的某个级别返回后代【英文标题】:Optimizing CTE to return descendants at certain level of parent-child hierarchy 【发布时间】:2012-03-27 03:00:35 【问题描述】:

我在 SQL Server 2008 R2 中定义了一个简单的临时表,表示父子关系。可以有多个层次结构(例如,最多 10 个)。我正在使用 CTE 在我的表中查找在后代层次结构中至少处于第 3 级的子级 - 换句话说,至少有一个父级和一个祖父级。

这是一个演示设置和我正在使用的 CTE 的脚本:

set nocount on
create table #linkage(entity_key bigint, parent_key bigint)
--alter table #linkage add foreign key (parent_key) references #linkage(entity_key)

insert into #linkage values(1, 1), (2, 2), (3, 3), (4, 1), (5, 4), (6, 5)
print 'all data:' select * from #linkage

print 'level 3+ descendents:' 
;with r(entity_key, parent_key, level) as
(
    select entity_key, parent_key, 1
        from #linkage
        where entity_key = parent_key
    union all
    select p.entity_key, r.parent_key, r.level + 1
        from #linkage p
        inner join r on p.parent_key = r.entity_key 
        where p.entity_key <> r.entity_key
)
select entity_key, parent_key as ultimate_parent_key
from r
where r.level > 2

正确输出如下:

all data:
entity_key           parent_key
-------------------- --------------------
1                    1
2                    2
3                    3
4                    1
5                    4
6                    5

level 3+ descendents:
entity_key           ultimate_parent_key  level
-------------------- -------------------- -----------
5                    1                    3
6                    1                    4

问题是我需要它来处理大型数据集。当我针对 1200 万行运行此操作时,需要 3 多分钟才能完成,我希望能显着减少。

我尝试创建聚集索引和非聚集索引 (entity_key)、(entity_key, parent_key) 等的各种组合,但似乎没有任何帮助(实际上,有些索引似乎减慢了速度)。

这是针对 1200 万行没有索引的执行计划:

  |--Filter(WHERE:([Recr1014]>(2)))
       |--Index Spool(WITH STACK)
            |--Concatenation
                 |--Compute Scalar(DEFINE:([Expr1015]=(0)))
                 |    |--Compute Scalar(DEFINE:([Expr1004]=(1)))
                 |         |--Table Scan(OBJECT:([tempdb].[dbo].[#linkage]), WHERE:([tempdb].[dbo].[#linkage].[entity_key]=[tempdb].[dbo].[#linkage].[parent_key]))
                 |--Assert(WHERE:(CASE WHEN [Expr1017]>(100) THEN (0) ELSE NULL END))
                      |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1017], [Recr1008], [Recr1009], [Recr1010]))
                           |--Compute Scalar(DEFINE:([Expr1017]=[Expr1016]+(1)))
                           |    |--Table Spool(WITH STACK)
                           |--Compute Scalar(DEFINE:([Expr1011]=[Recr1010]+(1)))
                                |--Filter(WHERE:([tempdb].[dbo].[#linkage].[entity_key] as [p].[entity_key]<>[Recr1008]))
                                     |--Index Spool(SEEK:([p].[parent_key]=[Recr1008]))
                                          |--Table Scan(OBJECT:([tempdb].[dbo].[#linkage] AS [p]))

以下是 XML 格式的相同计划,以防您遇到这种情况:

http://pastebin.com/Kx559C10

我还应该注意,这个盒子有 12 个 CPU,所以如果有什么方法可以引入一些并行性,那么这可能会有所帮助。

谁能推荐一种加快速度的方法?

【问题讨论】:

【参考方案1】:

您是否尝试过在parent_key 上建立索引并将entity_key 添加为包含列?

用 NULL 父节点标记根节点,而不是指向它们自己,应该会有所帮助:

declare @linkage table (entity_key bigint, parent_key bigint null) 

insert into @linkage values
  (1, NULL), (2, NULL), (3, NULL), (4, 1), (5, 4), (6, 5), (7, 3), (8, 7), (9, 5) 

;with r(entity_key, immediate_parent, root, level) as 
(
  -- Faster search for NULL to find roots.
  select entity_key, entity_key as immediate_parent, entity_key as root, 1 
    from @linkage 
    where parent_key is NULL
  union all
  -- No WHERE clause needed.
  select p.entity_key, r.entity_key, r.root, r.level + 1 
    from r inner join
      @linkage as p on p.parent_key = r.entity_key  
) 
select *
  from r

【讨论】:

出于好奇,在您的环境中,3 分钟的运行时间缩短到了多少? 大约2:30,包括建立索引所需的额外时间。

以上是关于优化 CTE 以在父子层次结构的某个级别返回后代的主要内容,如果未能解决你的问题,请参考以下文章

如何从父子层次结构表创建查询

Hive - 将层次结构表展平为级别

CTE递归获取树层次结构

层次选择器

层次选择器

选择器—文档结构