每次遇到这种类型的查询时,我总是想知道 SQL Server 将如何解决它。如果我运行需要计算的任何类型的查询,然后在多个位置使用该值,例如在select和 中order by,SQL Server 会为每一行计算两次还是会被缓存?此外,这如何与用户定义的函数一起工作?
例子:
SELECT CompanyId, Count(*)
FROM Sales
ORDER BY Count(*) desc
SELECT Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STX, Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STY
FROM Table
SELECT Id, udf.MyFunction(Id)
FROM Table
ORDER BY udf.MyFunction(Id)
Run Code Online (Sandbox Code Playgroud)
有没有办法让它更有效率,或者 SQL Server 是否足够聪明来为我处理它?
Joe*_*ish 12
SQL Server 查询优化器可以将重复的计算值组合到单个计算标量运算符中。它是否会这样做取决于查询计划成本和计算值的属性。正如预期的那样,它不会对不确定的计算值执行此操作,例如RAND(). 对于用户定义的函数,它也不会这样做。
我将从一个用户定义的函数示例开始。这是用户定义函数的一个很好的例子:
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
Run Code Online (Sandbox Code Playgroud)
我还想创建一个表并将 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;
Run Code Online (Sandbox Code Playgroud)
该dbo.NULL_FUNCTION函数是确定性的。以下查询将执行多少次?
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
Run Code Online (Sandbox Code Playgroud)
根据查询计划,这将为每一行执行一次,或 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');
Run Code Online (Sandbox Code Playgroud)
结果是 100,所以函数被执行了 100 次。
让我们尝试另一个简单的查询:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
Run Code Online (Sandbox Code Playgroud)
查询计划建议该函数将执行 200 次:
结果sys.dm_exec_function_stats表明该函数执行了 200 次。
请注意,您不能总是使用查询计划来计算执行计算标量的次数。以下引述来自“计算标量、表达式和执行计划性能”:
这导致人们认为 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;
Run Code Online (Sandbox Code Playgroud)
查询计划建议将计算一次:
然而,DMV 揭露了真相。计算标量被推迟到需要时,这在连接运算符中。它被评估 100 次。
您还询问了如何鼓励优化器避免多次重新计算相同的表达式。您能做的最好的事情是避免在代码中使用标量 UDF。除了这个问题之外,它们还有许多性能问题,包括夸大内存授权、强制整个查询使用MAXDOP 1、错误的基数估计以及导致额外的 CPU 使用率。如果您确实需要使用 UDF 并且该 UDF 的值是一个常量,您可以在查询之外计算它并将其放入局部变量中。
对于没有 UDF 的查询,您可以尝试避免编写返回相同结果但键入方式不同的表达式。对于下一个示例,我使用的是公开可用的 AdventureworksDW2016CTP3 数据库,但实际上任何数据库都可以。COUNT(*)此查询将计算多少次?
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Run Code Online (Sandbox Code Playgroud)
对于这个查询,我们可以通过查看哈希匹配(聚合)运算符来解决这个问题。
将COUNT(*)计算一次的每个独特的价值OrderDateKey。包含ORDER BY子句不会导致它被计算两次。您可以在此处查看执行计划。
现在,考虑一个将返回完全相同结果但以不同方式编写的查询:
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Run Code Online (Sandbox Code Playgroud)
查询优化器不够聪明,无法将它们组合起来,因此将完成额外的工作:
| 归档时间: |
|
| 查看次数: |
1292 次 |
| 最近记录: |