SQL Server查询优化器可以将重复的计算值组合到单个Compute Scalar运算符中。是否执行此操作取决于查询计划成本和计算值的属性。不出所料,它不会对不确定的计算值执行此操作,例如的例外RAND()
。对于用户定义的功能,它也不会这样做。
我将从一个用户定义的函数示例开始。这是用户定义函数的一个很好的例子:
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
我还想创建一个表并将100行放入其中:
CREATE TABLE X_100 (N BIGINT NOT NULL);
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
INSERT INTO X_100 WITH (TABLOCK)
SELECT n
FROM Nums WHERE n <= 100;
该dbo.NULL_FUNCTION
功能是determistic。以下查询将执行多少次?
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
根据查询计划,每行将执行一次,或执行100次:
SQL Server 2016引入了sys.dm_exec_function_stats DMV。我们可以对该DMV进行快照,以查看查询执行一次UDF的次数。
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');
结果为100,因此该函数已执行100次。
让我们尝试另一个简单的查询:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
查询计划建议该函数将执行200次:
结果sys.dm_exec_function_stats
表明该函数已执行200次。
请注意,您不能始终使用查询计划来计算执行计算标量的次数。以下引用来自“ 计算标量,表达式和执行计划的性能 ”:
这使人们认为Compute Scalar的行为与大多数其他运算符一样:随着行流经它,Compute Scalar包含的所有计算结果都将添加到流中。通常情况并非如此。尽管名称如此,但Compute Scalar并不总是计算任何东西,也不总是包含单个标量值(例如,它可以是向量,别名甚至是布尔谓词)。通常,“计算标量”只是定义一个表达式。实际的计算将推迟到执行计划中的某些后续结果需要为止。
让我们尝试另一个示例。对于以下查询,我希望对UDF进行一次计算:
WITH NULL_FUNCTION_CTE (NULL_VALUE) AS
(
SELECT DISTINCT dbo.NULL_FUNCTION(0)
)
SELECT n , cte.NULL_VALUE
FROM X_100
CROSS JOIN NULL_FUNCTION_CTE cte;
查询计划建议将其计算一次:
但是,DMV揭示了事实。计算标量被推迟到需要时才在连接运算符中。它被评估100次。
您还询问如何可以鼓励优化程序避免多次重新计算相同的表达式。最好的办法是避免在代码中使用标量UDF。这些问题在此问题之外还存在许多性能问题,包括扩大内存授权,迫使整个查询以的方式运行MAXDOP 1
,基数估计不正确以及导致额外的CPU利用率。如果确实需要使用UDF,并且该UDF的值是一个常量,则可以在查询之外进行计算并将其放入局部变量中。
对于没有UDF的查询,您可以尝试避免编写返回相同结果但输入方式不完全相同的表达式。对于下一个示例,我使用的是公开可用的AdventureworksDW2016CTP3数据库,但实际上任何数据库都可以。COUNT(*)
此查询将计算多少次?
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
对于此查询,我们可以通过查看哈希匹配(聚合)运算符来解决。
将COUNT(*)
计算一次的每个独特的价值OrderDateKey
。包含该ORDER BY
子句不会导致对其进行两次计算。您可以在此处查看执行计划。
现在,考虑一个查询,该查询将返回完全相同的结果,但以不同的方式编写:
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
查询优化器不足以将它们组合在一起,因此将进行其他工作: