SQL Server 如何优化 Hierarchyid::IsDescendantOf() 上的 JOIN?

Yan*_*tle 6 sql-server execution-plan sql-server-2019

我有具有树结构的表(由hierarchyid列定义),我想选择特定记录的所有后代。为此,我正在使用hiearchyid.IsDescendantOf()方法。

我预计,由于我不进行简单的比较,但我正在执行操作(在本例中我调用该IsDescendantOf()方法),那么我将得到一些带有索引扫描等的可怕执行计划。

然而,SQL Server 将其优化为漂亮的小索引查找。

执行计划

我很困惑为什么以及如何。

CLR 类型上的调用方法通常会被优化吗?我假设 SQL Server 将 CLR 类型视为不透明的黑匣子,因此无法发挥其魔力。(因为它也无法在本机 SQL 函数上执行此操作。)

或者这仅适用于该特定方法?(由于这些hieararchyid值是按深度优先排序的,因此我只需通过比较就可以获得类似的结果。

演示:

CREATE TABLE dbo.HierarchyExample (
    Id INT PRIMARY KEY,
    Hieararchy HIERARCHYID NOT NULL
);

INSERT INTO dbo.HierarchyExample(Id, Hieararchy)
VALUES
(1,  hierarchyid::Parse('/1/')),
(2,  hierarchyid::Parse('/1/1/')),
(3,  hierarchyid::Parse('/1/2/')),
(4,  hierarchyid::Parse('/1/3/')),
(5,  hierarchyid::Parse('/1/3/1/')),
(6,  hierarchyid::Parse('/1/3/2/')),
(7,  hierarchyid::Parse('/1/3/3/')),
(8,  hierarchyid::Parse('/1/4/')),
(9,  hierarchyid::Parse('/1/4/1/')),
(10, hierarchyid::Parse('/1/4/2/'));

CREATE INDEX IX_HierarchyExample_Hierarchy 
ON dbo.HierarchyExample (Hieararchy);

SELECT descendant.*
FROM HierarchyExample ancestor
    INNER JOIN HierarchyExample descendant
        ON descendant.Hieararchy.IsDescendantOf(ancestor.Hieararchy) = 1
WHERE ancestor.Id = 1

DROP TABLE IF EXISTS dbo.HierarchyExample;
Run Code Online (Sandbox Code Playgroud)

执行计划

小提琴

Mar*_*ith 5

descendant.Hieararchy.IsDescendantOf(ancestor.Hieararchy) = 1当然看起来它不应该是可控制的,但它似乎在这个特定案例的过程中很早就做了一些恶作剧。

如果我尝试

declare @x hierarchyid;     

SELECT *
FROM HierarchyExample descendant
WHERE descendant.Hieararchy.IsDescendantOf(@x) = 1
OPTION (querytraceon 3604, querytraceon 8605, querytraceon 8606);
Run Code Online (Sandbox Code Playgroud)

由此产生的执行计划显示了对

Seek Keys[1]: Start: [tempdb].[dbo].[HierarchyExample].Hieararchy >= Scalar Operator([@x]), 
               End:  [tempdb].[dbo].[HierarchyExample].Hieararchy <= Scalar Operator([@x].DescendantLimit())
Run Code Online (Sandbox Code Playgroud)

范围表达式已存在于转换后的树中。

转换后的树是来自解析器的解析树,导入并转换(“转换”)为早期编译阶段方便使用的树结构。

*** Converted Tree: ***
    LogOp_Project QCOL: [descendant].Id QCOL: [descendant].Hieararchy

        LogOp_Select

            LogOp_Get TBL: HierarchyExample(alias TBL: descendant) HierarchyExample TableID=1093578934 TableReferenceID=0 IsRow: COL: IsBaseRow1000 

            ScaOp_Logical x_lopAnd

                ScaOp_Comp x_cmpLe

                    ScaOp_Identifier COL: @x 

                    ScaOp_Identifier QCOL: [descendant].Hieararchy

                ScaOp_Comp x_cmpLe

                    ScaOp_Identifier QCOL: [descendant].Hieararchy

                    ScaOp_UdtFunction EClrFunctionType_UdtMethodDescendantLimit IsDet NoDataAccess  TI(hierarchyid,Null,Var,ML=892) 

                        ScaOp_Identifier COL: @x 

        AncOp_PrjList 

Run Code Online (Sandbox Code Playgroud)

所以我得出的结论是,在解析过程中,它会转换为潜在的可查找范围谓词。

在论文《对灵活模式场景的关系支持》中提到了这种转换,尽管它没有详细介绍。

为了支持所考虑的场景,我们定义了一个特定的操作,该操作在关系处理发生之前在内部重写。H1列是 的后代的事实H2由 表示, H1.IsDescendant(H2)并且翻译后成为范围谓词:H2 >= H1 and H2 <= H1.DescendantLimit()

此后优化就会正常进行。之后使用的查询转换规则只是SelIdxToRng-SelToTrivialFilter没有任何特定于 HierarchyId 的规则。

下面是更复杂的连接示例的转换后的树,显示的内容大致相同

*** Converted Tree: ***
    LogOp_Project QCOL: [descendant].Id QCOL: [descendant].Hieararchy

        LogOp_Select

            LogOp_Join

                LogOp_Get TBL: HierarchyExample(alias TBL: ancestor) HierarchyExample TableID=1093578934 TableReferenceID=0 IsRow: COL: IsBaseRow1000 

                LogOp_Get TBL: HierarchyExample(alias TBL: descendant) HierarchyExample TableID=1093578934 TableReferenceID=0 IsRow: COL: IsBaseRow1001 

                ScaOp_Logical x_lopAnd

                    ScaOp_Comp x_cmpLe

                        ScaOp_Identifier QCOL: [ancestor].Hieararchy

                        ScaOp_Identifier QCOL: [descendant].Hieararchy

                    ScaOp_Comp x_cmpLe

                        ScaOp_Identifier QCOL: [descendant].Hieararchy

                        ScaOp_UdtFunction EClrFunctionType_UdtMethodDescendantLimit IsDet NoDataAccess  TI(hierarchyid,Null,Var,ML=892) 

                            ScaOp_Identifier QCOL: [ancestor].Hieararchy

            ScaOp_Comp x_cmpEq

                ScaOp_Identifier QCOL: [ancestor].Id

                ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=1)

        AncOp_PrjList 

*******************
Run Code Online (Sandbox Code Playgroud)