Trình tối ưu hóa truy vấn SQL Server có thể kết hợp các giá trị được tính toán lặp lại vào một toán tử vô hướng tính toán duy nhất. Việc nó có làm điều này hay không phụ thuộc vào chi phí kế hoạch truy vấn và các thuộc tính của giá trị được tính toán. Như mong đợi, nó sẽ không làm điều này cho các giá trị được tính toán không đặc trưng, mà một vài ngoại lệ như RAND(). Nó cũng sẽ không làm điều này cho các chức năng do người dùng xác định.
Tôi sẽ bắt đầu với một ví dụ chức năng do người dùng định nghĩa. Đây là một ví dụ tuyệt vời về chức năng do người dùng xác định:
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
Tôi cũng muốn tạo một bảng và đặt 100 hàng vào đó:
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;
Các dbo.NULL_FUNCTIONchức năng là xác định. Bao nhiêu lần nó sẽ được thực hiện cho truy vấn sau đây?
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
Dựa trên kế hoạch truy vấn, điều này sẽ được thực hiện một lần cho mỗi hàng hoặc 100 lần:

SQL Server 2016 đã giới thiệu sys.dm_exec_feft_stats DMV. Chúng ta có thể chụp ảnh nhanh của DMV đó để xem UDF được thực hiện bao nhiêu lần bởi một truy vấn.
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');
Kết quả là 100, vì vậy hàm được thực thi 100 lần.
Hãy thử một truy vấn đơn giản khác:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
Kế hoạch truy vấn cho thấy chức năng sẽ được thực hiện 200 lần:

Kết quả sys.dm_exec_function_statscho thấy hàm đã được thực thi 200 lần.
Lưu ý rằng bạn không thể luôn luôn sử dụng kế hoạch truy vấn để tìm ra số lần tính toán vô hướng được thực hiện. Trích dẫn sau đây là từ " Tính toán vô hướng, biểu thức và hiệu suất kế hoạch thực hiện ":
Điều này khiến mọi người nghĩ rằng Compute Scalar hoạt động giống như phần lớn các toán tử khác: khi các hàng chảy qua nó, kết quả của bất kỳ tính toán nào mà Compute Scalar chứa được thêm vào luồng. Điều này thường không đúng. Mặc dù tên như vậy, Compute Scalar không phải lúc nào cũng tính toán bất cứ thứ gì và không phải lúc nào cũng chứa một giá trị vô hướng duy nhất (ví dụ, nó có thể là một vectơ, bí danh hoặc thậm chí là một vị từ Boolean). Thường xuyên hơn không, một tính toán vô hướng đơn giản xác định một biểu thức; tính toán thực tế được hoãn lại cho đến khi một cái gì đó sau này trong kế hoạch thực hiện cần kết quả.
Hãy thử một ví dụ khác. Đối với truy vấn sau đây, tôi hy vọng rằng UDF được tính một lần:
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;
Kế hoạch truy vấn cho thấy rằng nó sẽ được tính một lần:

Tuy nhiên, DMV tiết lộ sự thật. Vô hướng tính toán được hoãn lại cho đến khi cần, nằm trong toán tử nối. Nó được đánh giá 100 lần.
Bạn cũng đã hỏi những gì bạn có thể làm để khuyến khích trình tối ưu hóa để tránh tính toán lại cùng một biểu thức nhiều lần. Điều tốt nhất mà bạn có thể làm là tránh sử dụng các UDF vô hướng trong mã của bạn. Những vấn đề này có một số vấn đề về hiệu năng ngoài câu hỏi này, bao gồm các khoản trợ cấp bộ nhớ tăng cao, buộc toàn bộ truy vấn phải chạy với MAXDOP 1, ước tính cardinality xấu và dẫn đến việc sử dụng CPU bổ sung. Nếu bạn cần sử dụng UDF và giá trị của UDF đó là hằng số, bạn có thể tính toán nó bên ngoài truy vấn và đặt nó vào một biến cục bộ.
Đối với các truy vấn không có UDF, bạn có thể cố gắng tránh viết các biểu thức trả về cùng một kết quả nhưng không được nhập chính xác theo cùng một cách. Trong ví dụ tiếp theo này, tôi đang sử dụng cơ sở dữ liệu AdventureworksDW2016CTP3 có sẵn công khai, nhưng thực sự bất kỳ cơ sở dữ liệu nào cũng sẽ làm được. Bao nhiêu lần sẽ COUNT(*)được tính cho truy vấn này?
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Đối với truy vấn này, chúng ta có thể tìm ra điều này bằng cách xem toán tử Hash Match (tổng hợp).

Các COUNT(*)được tính một lần cho mỗi giá trị duy nhất của OrderDateKey. Bao gồm các ORDER BYđiều khoản không làm cho nó được tính hai lần. Bạn có thể xem kế hoạch thực hiện ở đây .
Bây giờ, hãy xem xét một truy vấn sẽ trả về cùng một kết quả nhưng được viết theo một cách khác:
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
Trình tối ưu hóa truy vấn không đủ thông minh để kết hợp chúng, vì vậy công việc bổ sung sẽ được thực hiện:
