递归CTE导致速度慢和索引扫描

时间:2017-04-05 16:16:25

标签: performance sql-server-2008 indexing sql-execution-plan recursive-query

我有一个包含位置信息的表(Location_Tree),安排在几个级别的Id / ParentId结构中,从“世界”的0级到各个城市,通过世界各地,国家,州,县,各州和城市。我们有另一个事件表,其中有一列“CreatedWithLocationId”,表示事件的位置,但没有什么可说的,所提供的位置是城市,县,国家还是其他 - 换句话说,准确性是未定义的。< / p>

我正在使用查询生成一个报告,该报告记录一个事件的位置并显示“附近”列出的其他事件。最佳匹配将是与输入位置共享位置的匹配,但如果找不到足够的匹配,我将“缩小”该位置,在树上搜索父位置,直到找到足够的匹配报告要求

为了实现这一点,我做了一个递归CTE,它将从Location_Tree表中获取给定位置的所有子节点。因此,我获取输入位置,找到parentId,然后找到共享该parentId的所有位置,或者将其作为祖父母等等,按级别限制等等。

DECLARE @Ancestor_Id INT = 1;

WITH Location_Children AS
(
SELECT @Ancestor_Id AS Id, NULL AS ParentId
UNION ALL
 SELECT
    B.Id, B.ParentId
 FROM       Location_Tree       AS B
 INNER JOIN Location_Children   AS C    ON B.ParentId = C.Id
)

SELECT * FROM Location_Children;

即使返回的唯一行是@Ancestor_Id AS Id, NULL AS ParentId行,上面的查询总是会导致对Location_Tree table()的主键和急切的假脱机进行聚簇索引扫描 - 所有执行计划涉及大量行,查询大约需要15秒才能完成,返回我的一行。

--> Execution Plan

有没有人对我如何加快速度提出任何建议?我已经尝试过添加索引等等,而且我有点不愿意使用CASE语句或一系列左连接来替换CTE进行大量查询,因为它需要大量的脚本编写。我已经尝试了这个查询的每个排列,使用内联函数,自定义表数据类型,(几乎)一切都无济于事......接下来应该尝试什么?

1 个答案:

答案 0 :(得分:0)

如果Eager Spool消耗了大量的查询费用,那么查看Itzik Ben-Gan在Divide and Conquer Halloween: Avoid the Overhead of Halloween Protection上的帖子可能会有所帮助。万圣节问题发生在涉及非聚簇索引的UPDATE语句之类的情况下,其中查询优化器可能引用与更新值不同步的索引值,从而导致不正确的数据甚至无限循环。 Eager Spool是SQL Server查询优化器在万圣节保护等情况下使用的阻塞运算符。在他的excellent four-part series(Ben-Gan也链接到)关于万圣节问题,Paul White解释了对Eager Spools的需求:

  

&#34;没有提示或跟踪标志来防止包含线轴   这个执行计划,因为它是正确性所必需的。就像它一样   顾名思义,假脱机急切地消耗其孩子的所有行   在将行返回到其父Compute之前,运算符(Index Seek)   标量。这样做的效果是引入完全相分离 -   读取所有符合条件的行并将其保存到临时存储中   任何更新都会被执行。&#34;

它们有时也需要递归CTE,它们更新内部工作表(如果我没记错的话,在TempDB中)。大多数消息来源说,你基本上坚持了他们;我已经能够通过彻底索引连接中涉及的所有列来删除昂贵的Eager Spools,但由此产生的执行计划迄今为止没有任何更快,并且表现出几乎拜占庭式的复杂性;性能损失仅仅转移到同样昂贵的Concatenate运营商。一些消息来源表示,WITH(NOLOCK)或WITH(READ UNCOMMITTED)可能有所帮助,但我对这些没有任何好运;他们每次都会产生完全相同的执行计划,而昂贵的Eager Spools仍然存在。我不想抄袭Ben-Gan的代码,特别是因为我还没有尝试过,但他的解决方法是完全抛弃递归CTE方法并对一对临时表进行递归更新或表变量,以欺骗优化器去除万圣节保护机制。几天前,我在一对关键的长期运行查询中遇到了这个问题,从未成功删除过Eager Spools;我只能通过无情地从查询和其他此类优化中清除不必要的重复项来运行它们,所有这些都需要数小时的实验,并且在递归CTE的连接中添加了许多复杂的CASE语句以减少正在处理的不必要行数。我正在研究的查询是生成递归CTE,它们创建了连续的数据排列;在某些方面,我能够通过预先计算确定排列的数字来降低总体计算成本,但这可能不适用于您的情况,即父子检索CTE。

我希望自己能够更加鼓舞人心,但不管怎么说,你可能会遇到一个昂贵的阻塞Eager Spool,除非你使用像Ben-Gan那样的大胆的解决方法来抛弃递归的CTE完全接近。如果要保留递归CTE方法,您唯一的选择可能是提供复杂的逻辑,以减少正在处理的不必要的行数。在连接条件上使用非聚簇索引也可能会带来一些好处,但基本上你正在研究一系列零碎的优化,这些优化可能很费时间。