具有高扫描计数和逻辑读取的递归CTE

时间:2018-02-22 10:54:38

标签: sql sql-server tsql sql-server-2012 query-performance

我试图在没有运气的情况下优化下面的递归CTE。该表有5079条记录。

;WITH CTE_REC AS (

     SELECT         
          ID
        , ParentId
        , ID as ChildId
        , IsActive          
        FROM
        #temp

    UNION ALL


    SELECT 
         C.ID
        , C.ParentId    
        , H.ChildId 
        ,H.IsActive         
    FROM 
        #temp AS C
        INNER JOIN
        CTE_REC H ON C.ID = H.ParentId  
)
SELECT * FROM CTE_REC

上述查询的执行计划是:

enter image description here

IO统计数据是:

(25441 row(s) affected)
Table 'Worktable'.
Scan count 20365, logical reads 193768, physical reads 0,
read-ahead reads 0, lob logical reads 0, lob physical reads 0,
lob read-ahead reads 0.
Table '#temp_______________________________________________________________________________________________________________000000001B2D'.
Scan count 2, logical reads 34, physical reads 0, read-ahead reads 17,
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

我在临时表上创建了以下索引。

CREATE INDEX IX_TEMP ON #Temp(Id,ParentId)

创建索引后,执行计划如下所示。

enter image description here

索引后的IO统计数据:

Table '#temp_______________________________________________________________________________________________________________000000001B2D'.
Scan count 20364, logical reads 40776, physical reads 0, read-ahead reads 0,
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'.
Scan count 2, logical reads 142778, physical reads 0, read-ahead reads 0,
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

仍然在索引之后有高扫描计数和逻辑读取。 CTE返回25411行,我没有发现CPU时间的任何差异,有/无索引为400毫秒。

3 个答案:

答案 0 :(得分:0)

该递归只是服务器的很多步骤。

我只想在ID上放置一个聚集的PK。

将父F上的FK放入ID可能会有所帮助,这可能会有所帮助。

在你可以放置where ParentId is not null的锚点中,但是它们不会有很多,它会从报告中删除它们。

在锚点中,您只能过滤到没有人向他们报告的人。你仍然得到所有的链条。当我的老板与我的同一个链条时,在我的老板上有一个单独的链是有点愚蠢的。

计算码头的链条也很浪费。如果我们有一个共同的老板,那么我们就拥有相同的链条。这里3和6具有相同的链。在下面的示例中,您只需要锚定:

select min(e.id) as 'modelGrunt', e.mgr
  from @emp e  
 where not exists (select 1 from @emp e1 where e1.mgr = e.id)
 group by e.mgr;

根据这些信息,您可以构建每个链。运行它并实现它。这是一个更复杂的查询,但将递归行的数量减少到接近最小值。它不是一个完整的最小值,因为你可能会发疯,甚至不会重复子链。

我几乎一样,但是数量不多,这不是问题。您需要使用排序进行优化,否则结果不会以有意义的方式进行分组。这有索引搜索和索引扫描。

declare @emp table (id  int primary key, mgr int);  
insert into @emp values 
       (1, null)
     , (2, 1)
     , (3, 2)
     , (4, null)
     , (5, 4) 
     , (6, 2);
--select * from @emp;

; with cte as 
( select e.id ori, e.id, e.mgr, cnt = 1 
    from @emp e  
  union all 
  select cte.ori,  e.id, e.mgr, cnt + 1
    from @emp e 
    join cte 
      on cte.mgr = e.id 
) 

 select ori, id, mgr, cnt  
 from cte  
 order by ori, cnt;

答案 1 :(得分:0)

您的锚点不太正确,您需要将顶层限制为仅非子项目的行:

;WITH CTE_REC AS (
     SELECT         
          ID
        , ParentId
        , ID as ChildId
        , IsActive          
        FROM #temp
        WHERE ParentId IS NULL
    UNION ALL
    SELECT 
         C.ID
        , C.ParentId    
        , H.ChildId 
        ,H.IsActive         
    FROM #temp AS C
    INNER JOIN CTE_REC H 
       ON C.ID = H.ParentId  
    WHERE C.ParentId IS NOT NULL
)
SELECT * FROM CTE_REC

答案 2 :(得分:-1)

您是否尝试在临时表上创建聚簇索引?您创建的索引是非群集的,这意味着您的临时表仍然是一个堆,因此高扫描计数,因为查询将扫描堆以对ChildID,IsActive等进行密钥查找。

在临时表上的ID,ParentID上创建聚簇索引(CREATE CLUSTERED INDEX)。 然后为ParentID,ID,ChildID,IsActive添加覆盖非聚集索引。您可能需要测试此NCI,因为最好将ID移到最后。