SQL Server có tính toán các giá trị trong một truy vấn không?


10

Mỗi lần tôi gặp phải loại truy vấn này, tôi luôn tự hỏi SQL Server sẽ xử lý nó như thế nào. Nếu tôi chạy bất kỳ loại truy vấn nào yêu cầu tính toán và sau đó sử dụng giá trị đó ở nhiều nơi, ví dụ như trong selectorder by, SQL Server sẽ tính toán nó hai lần cho mỗi hàng hay nó sẽ được lưu vào bộ đệm? Hơn nữa, làm thế nào để nó hoạt động với các chức năng do người dùng xác định?

Ví dụ:

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)

Có cách nào để làm cho nó hiệu quả hơn hay SQL Server đủ thông minh để xử lý nó cho tôi không?


"Nó phụ thuộc" đây là một cuộc triển lãm rextester.com/DXOB90032
Martin Smith

Bạn có thể so sánh với rextester.com/ARSO25902
Martin Smith ngày

@MartinSmith bạn không sử dụng chức năng không xác định? Nếu đúng như vậy thì tôi mong SQL sẽ thực thi nó hai lần.
Jonas Stawski ngày

luôn có một ngoại lệ Bạn có thể thử SELECT RAND() FROM Sales order by RAND()- điều này chỉ được đánh giá một lần vì nó không mang tính quyết định và hằng số thời gian chạy.
Martin Smith

Câu trả lời:


11

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:

kế hoạch truy vấn 1

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ế hoạch truy vấn 2

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:

kế hoạch truy vấ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).

băm khớ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:

băm khớp 2

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.