这是 SQL 人员的脑筋急转弯 - 谁能想到为什么第一个函数执行良好,而第二个函数运行缓慢的原因?
功能 A - 通常在 ~5 毫秒内完成
CREATE FUNCTION dbo.GoodFunction
(
@IDs UniqueIntTable READONLY
)
RETURNS TABLE
AS RETURN
SELECT p.ID, p.Node, p.Name, p.Level
FROM
(
SELECT DISTINCT a.Ancestor AS Node
FROM Hierarchy h
CROSS APPLY dbo.GetAncestors(h.Node.GetAncestor(1)) a
WHERE h.ID IN (SELECT Value FROM @IDs)
) np
INNER JOIN Hierarchy p
ON p.Node = np.Node
功能 B - 运行非常缓慢 - 5 分钟后我放弃了
CREATE FUNCTION dbo.BadFunction
(
@IDs UniqueIntTable READONLY
)
RETURNS TABLE
AS RETURN
WITH Ancestors_CTE AS
(
SELECT DISTINCT a.Ancestor AS Node
FROM Hierarchy c
CROSS APPLY dbo.GetAncestors(c.Node.GetAncestor(1)) a
WHERE c.ID IN (SELECT Value FROM @IDs)
)
SELECT p.ID, p.Node, p.Name, p.Level
FROM Ancestors_CTE ac
INNER JOIN Hierarchy p
ON p.Node = ac.Node
我将在下面解释这个函数的作用,但在我开始之前,我想指出我认为这并不重要,因为据我所知,这两个函数是完全相同的! 唯一的区别是一个使用 CTE,一个使用子查询;A中子查询的内容和B中的CTE是相同的。
如果有人决定这很重要:此函数的目的只是挑选出层次结构中任意数量位置的所有可能的祖先(父母、祖父母等)。该Node
列是一个hierarchyid
,并且dbo.GetAncestors
是一个 CLR 函数,它只是沿着路径走,它不做任何数据访问。
UniqueIntTable
这就是它所暗示的 - 它是一个用户定义的表类型,只有一列,Value int NOT NULL PRIMARY KEY
. 这里所有应该被索引的东西都被索引了——函数 A 的执行计划本质上只是两个索引搜索和一个哈希匹配,就像函数 B 一样。
这个奇怪问题的一些更奇怪的方面:
我什至无法使用函数 B 获得一个简单查询的估计执行计划。看起来性能问题与这个看似简单的函数的编译有关。
如果我从函数 B 中取出“主体”并将其粘贴到内联查询中,它会正常运行,与函数 A 的性能相同。所以它似乎只是UDF 内的 CTE 的问题,或者相反,只有使用 CTE 的 UDF。
当我尝试运行 B 时,测试机器上一个内核上的 CPU 使用率一直飙升至 100%。似乎没有太多 I/O。
我想将其视为 SQL Server 错误并使用版本 A,但我总是尝试牢记规则 #1(“SELECT Ain't Broken”),并且我担心函数 A 的良好结果某种程度上是一种本地化的侥幸,它会像 B 在不同的服务器上一样“失败”。
有任何想法吗?
更新- 我现在包含一个完整的独立脚本来重现。
获取祖先函数
[SqlFunction(FillRowMethodName = "FillAncestor",
TableDefinition = "Ancestor hierarchyid", IsDeterministic = true,
IsPrecise = true, DataAccess = DataAccessKind.None)]
public static IEnumerable GetAncestors(SqlHierarchyId h)
{
while (!h.IsNull)
{
yield return h;
h = h.GetAncestor(1);
}
}
模式创建
BEGIN TRAN
CREATE TABLE Hierarchy
(
ID int NOT NULL IDENTITY(1, 1)
CONSTRAINT PK_Hierarchy PRIMARY KEY CLUSTERED,
Node hierarchyid NOT NULL,
[Level] as Node.GetLevel(),
Name varchar(50) NOT NULL
)
CREATE INDEX IX_Hierarchy_Node
ON Hierarchy (Node)
INCLUDE (Name)
CREATE INDEX IX_Hierarchy_NodeBF
ON Hierarchy ([Level], Node)
GO
INSERT Hierarchy (Node, Name)
SELECT CAST('/1/' AS hierarchyid), 'Alice' UNION ALL
SELECT CAST('/1/1/' AS hierarchyid), 'Bob' UNION ALL
SELECT CAST('/1/1/1/' AS hierarchyid), 'Charles' UNION ALL
SELECT CAST('/1/1/2/' AS hierarchyid), 'Dave' UNION ALL
SELECT CAST('/1/1/3/' AS hierarchyid), 'Ellen' UNION ALL
SELECT CAST('/1/2/' AS hierarchyid), 'Fred' UNION ALL
SELECT CAST('/1/3/' AS hierarchyid), 'Graham' UNION ALL
SELECT CAST('/1/3/1/' AS hierarchyid), 'Harold' UNION ALL
SELECT CAST('/1/3/2/' AS hierarchyid), 'Isabelle' UNION ALL
SELECT CAST('/1/4/' AS hierarchyid), 'John' UNION ALL
SELECT CAST('/2/' AS hierarchyid), 'Karen' UNION ALL
SELECT CAST('/2/1/' AS hierarchyid), 'Liam' UNION ALL
SELECT CAST('/2/2/' AS hierarchyid), 'Mary' UNION ALL
SELECT CAST('/2/2/1/' AS hierarchyid), 'Nigel' UNION ALL
SELECT CAST('/2/2/2/' AS hierarchyid), 'Oliver' UNION ALL
SELECT CAST('/2/3/' AS hierarchyid), 'Peter' UNION ALL
SELECT CAST('/2/3/1/' AS hierarchyid), 'Quinn'
GO
CREATE TYPE UniqueIntTable AS TABLE
(
Value int NOT NULL,
PRIMARY KEY (Value)
)
GO
COMMIT
GO
上述代码/脚本可用于创建 CLR 函数/DB 模式;使用与原版相同的GoodFunction
脚本BadFunction
。