【问题标题】:Question about SQL Server HierarchyID depth-first performance关于 SQL Server HierarchyID 深度优先性能的问题
【发布时间】:2011-02-12 10:35:50
【问题描述】:

我正在尝试在包含大约 50,000 行的表 (dbo.[Message]) 中实现 hierarchyID(将来会大幅增长)。然而,检索大约 25 个结果需要 30-40 秒。

根节点是为了提供唯一性而填充的,因此每个后续行都是该虚拟行的子节点。

我需要能够深度优先遍历表,并将 hierarchyID 列 (dbo.[Message].MessageID) 设为集群主键,还添加了一个计算 smallint (dbo.[Message].Hierarchy)存储节点的级别。

用法:.Net 应用程序通过 hierarchyID 值传递到数据库中,我希望能够检索该节点的所有(如果有的话)子和父节点(除了根,因为它是填充物)。

我正在使用的查询的简化版本:

@MessageID hierarchyID   /* passed in from application */

SELECT 
m.MessageID, m.MessageComment 

FROM 
dbo.[Message] as m

WHERE 
m.Messageid.IsDescendantOf(@MessageID.GetAncestor((@MessageID.GetLevel()-1))) = 1

ORDER BY 
m.MessageID

据我了解,应该会自动检测索引而无需提示。

从搜索论坛中,我看到人们在处理广度优先索引时使用索引提示,但没有在深度优先的情况下观察到此应用程序。这对我的场景来说是一种相关的方法吗?

过去几天我一直在尝试找到解决此问题的方法,但无济于事。 我将非常感谢任何帮助,因为这是我的第一篇文章,如果这被认为是一个“noobish”问题,我提前道歉,我已经阅读了 MS 文档并搜索了无数论坛,但没有找到简洁的描述具体问题。

【问题讨论】:

  • 顺便问一下,您有什么疑问?正如所写,它总是选择整个表中的所有节点。 @MessageID.GetAncestor(@MessageID.GetLevel() - 1) 一直到根,然后你选择所有的后代,也就是……一切。这就是它如此缓慢的原因。
  • 只是为了澄清一下:我的情况需要使用深度优先索引,抱歉混淆(我最后提到广度优先只是为了提供人们建议使用索引的示例-提示)

标签: sql tsql sql-server-2008 hierarchy hierarchyid


【解决方案1】:

在这里找到解决方法: http://connect.microsoft.com/SQLServer/feedback/details/532406/performance-issue-with-hierarchyid-fun-isdescendantof-in-where-clause#

提醒一下,我从应用程序传入的 heirarchyID 开始,我的目标是检索该值的所有亲属(祖先和后代)。

在我的具体示例中,我必须在 SELECT 语句之前添加以下声明:

declare @topNode hierarchyid = (select @messageID.GetAncestor((@messageID.GetLevel()-1)))
declare @topNodeParent hierarchyid = (select @topNode.GetAncestor(1))
declare @leftNode hierarchyid= (select @topNodeParent.GetDescendant (null, @topNode))
declare @rightNode hierarchyid= (select @topNodeParent.GetDescendant (@topNode, null))

WHERE 子句已更改为:

messageid.IsDescendantOf(@topNode)=1 AND (messageid > @leftNode ) AND (messageid < @rightNode )

查询性能提升非常显着:

对于传入的每个结果,寻道时间现在平均为 20 毫秒(从 120 到 420)。

在查询 25 个值时,之前需要 25 - 35 秒才能返回所有相关节点(在某些情况下,每个值都有很多亲戚,在某些情况下没有)。现在只需 2 秒。

非常感谢所有在本网站和其他网站上为此问题做出贡献的人。

【讨论】:

    【解决方案2】:

    目前尚不清楚您是在尝试优化深度优先搜索还是广度优先搜索;这个问题建议深度优先,但最后的 cmets 是关于广度优先的。

    您拥有深度优先所需的所有索引(只需索引hierarchyid 列)。对于广度优先,仅创建计算的level 列是不够的,您还必须对其进行索引:

    ALTER TABLE Message
    ADD [Level] AS MessageID.GetLevel()
    
    CREATE INDEX IX_Message_BreadthFirst
    ON Message (Level, MessageID)
    INCLUDE (...)
    

    (请注意,对于非聚集索引,您很可能需要 INCLUDE - 否则,SQL Server 可能会转而使用聚集索引扫描。)

    现在,如果您要查找一个节点的所有祖先,您需要采取稍微不同的方法。您可以使这些搜索变得闪电般快速,因为 - 这就是 hierarchyid 的酷炫之处 - 每个节点已经“包含”了它的所有祖先。

    我使用 CLR 函数尽可能快地完成此操作,但您可以使用递归 CTE 来完成:

    CREATE FUNCTION dbo.GetAncestors
    (
        @h hierarchyid
    )
    RETURNS TABLE
    AS RETURN
    WITH Hierarchy_CTE AS
    (
        SELECT @h AS id
    
        UNION ALL
    
        SELECT h.id.GetAncestor(1)
        FROM Hierarchy_CTE h
        WHERE h.id <> hierarchyid::GetRoot()
    )
    SELECT id FROM Hierarchy_CTE
    

    现在,要获取所有的祖先和后代,像这样使用它:

    DECLARE @MessageID hierarchyID   /* passed in from application */
    
    SELECT m.MessageID, m.MessageComment 
    FROM Message as m
    WHERE m.MessageId.IsDescendantOf(@MessageID) = 1
    OR m.MessageId IN (SELECT id FROM dbo.GetAncestors(@MessageID.GetAncestor(1)))
    ORDER BY m.MessageID
    

    试试看 - 这应该可以解决您的性能问题。

    【讨论】:

    • 很抱歉造成混乱,深度优先确实是我所追求的!非常感谢您的建议,我马上试试。
    • 仅出于测试目的,我已经完全删除了 @MessageID.GetAncestor,只在 WHERE 子句中留下:m.MessageId.IsDescendantOf(@MessageID) = 1 并且当我运行该 proc 时,查找时间仍然是每个结果在 150 到 420 毫秒之间,这对我的应用程序来说非常慢。性能是重中之重,我对 CLR 完全不熟悉,但是我真的很想学习如何实现它,如果这样可以提供最佳性能的话。关于从哪里开始有什么建议吗?
    • @AndalusianCat:CLR 版本用于祖先查询。如果您发现仅使用IsDescendantOf 会很慢,请发布实际查询、表模式(包括索引)和执行计划。 hierarchyid 查询通常比这快得多。
    猜你喜欢
    • 2011-07-01
    • 2010-12-27
    • 2012-08-29
    • 2013-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    相关资源
    最近更新 更多