【问题标题】:Recursive CTE with high scan count and logical reads具有高扫描计数和逻辑读取的递归 CTE
【发布时间】:2018-08-02 04:44:15
【问题描述】:

我正在尝试优化以下递归 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

上述查询的执行计划是:

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)

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

索引后的 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 毫秒有/无索引。

【问题讨论】:

  • 你应该减少你的锚的行数。添加WHERE ParentId IS NOT NULL 可能是一个不错的选择,但这实际上取决于您的数据和您的需求。
  • 为什么不使用hierarchyid 而不是递归?
  • 我不知道hierarchyid。你能给我任何关于hierarchyid的链接吗?
  • 您的查询似乎是错误的。非常高的 Cardianility 估计。您应该改进您的 Anchor 和递归两者。
  • 奇数格式。

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


【解决方案1】:

您是否尝试过在临时表上创建聚集索引。您创建的索引是非集群的,这意味着您的临时表仍将是一个堆,因此扫描计数很高,因为查询将扫描堆以执行 ChildID、IsActive 等的键查找。

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

【讨论】:

  • 你在什么上集群? ID?您还在使用您创建的 NCI 吗?尝试为 ParentID、ID、ChildID、IsActive 添加一个 NCI,而不是上面描述的那个
  • 没有。我删除了 NCI 并在 ID、ParentID 上创建了 CI。没用。
【解决方案2】:

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

我只会在 ID 上放置一个集群 PK。

将 ParentID 上的 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;

【讨论】:

    【解决方案3】:

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

    ;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
    

    【讨论】:

    • 数量不多,处理速度很快。问题是它们没有被直接报告。
    • @Paparazzi 不确定我是否理解此评论。请提供一些示例数据。不将锚限制为仅具有 null parentid 的行的问题是您将多次报告相同的行。例如如果 A->B->C 你会报告这个和 B->C 和 C。
    • 我不同意。我在答案中发布了数据。如果您添加您建议的空值,您将获得不同的报告。如果你想修剪链条,这只修剪链条的顶部。我没有投票给你。我认为这是有效的输入。
    • @Paparazzi 如果你使用这个“on cte.id = e.mgr”作为你的加入标准,你的加入是不正确的,那么你可以添加 where .. not null 到你的锚,然后锚得到正确报告。在您的查询中,id 2 被报告两次。
    • 不会和你争论。我已经运行了查询。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-31
    • 1970-01-01
    • 2017-03-30
    • 1970-01-01
    • 2015-09-10
    • 1970-01-01
    • 2022-01-01
    相关资源
    最近更新 更多