Tuần trước, tôi đã xuất bản một bài đăng có tên #BackToBasics:DATEFROMPARTS()
, nơi tôi đã chỉ ra cách sử dụng hàm 2012+ này cho các truy vấn phạm vi ngày rõ ràng hơn, có thể phân loại được. Tôi đã sử dụng nó để chứng minh rằng nếu bạn sử dụng vị từ ngày kết thúc mở và bạn có chỉ mục trên cột ngày / giờ liên quan, bạn có thể sử dụng chỉ mục tốt hơn nhiều và I / O thấp hơn (hoặc, trong trường hợp xấu nhất , tương tự, nếu không thể sử dụng tìm kiếm vì lý do nào đó hoặc nếu không có chỉ mục phù hợp):
Nhưng đó chỉ là một phần của câu chuyện (và nói rõ ràng, DATEFROMPARTS()
về mặt kỹ thuật không bắt buộc phải có một lượt tìm kiếm, nó chỉ rõ ràng hơn trong trường hợp đó). Nếu chúng tôi thu nhỏ một chút, chúng tôi nhận thấy rằng ước tính của chúng tôi không chính xác, một sự phức tạp mà tôi không muốn giới thiệu trong bài viết trước:
Điều này không có gì lạ đối với cả các vị từ bất đẳng thức và khi quét cưỡng bức. Và tất nhiên, phương pháp tôi đề xuất sẽ không mang lại số liệu thống kê không chính xác nhất sao? Đây là cách tiếp cận cơ bản (bạn có thể lấy lược đồ bảng, chỉ mục và dữ liệu mẫu từ bài đăng trước của tôi):
CREATE PROCEDURE dbo.MonthlyReport_Original @Year int, @Month int AS BEGIN SET NOCOUNT ON; DECLARE @Start date = DATEFROMPARTS(@Year, @Month, 1); DECLARE @End date = DATEADD(MONTH, 1, @Start); SELECT DateColumn FROM dbo.DateEntries WHERE DateColumn >= @Start AND DateColumn < @End; END GO
Giờ đây, các ước tính không chính xác không phải lúc nào cũng là vấn đề, nhưng nó có thể gây ra các vấn đề với các lựa chọn kế hoạch không hiệu quả ở hai thái cực. Một kế hoạch duy nhất có thể không tối ưu khi phạm vi đã chọn sẽ mang lại một tỷ lệ phần trăm bảng hoặc chỉ mục rất nhỏ hoặc rất lớn và điều này có thể rất khó để SQL Server dự đoán khi phân phối dữ liệu không đồng đều. Joseph Sack đã nêu ra những điều điển hình hơn mà ước tính xấu có thể ảnh hưởng trong bài đăng của anh ấy, "Mười mối đe dọa phổ biến đối với chất lượng kế hoạch thực thi:"
"[…] Ước tính hàng không hợp lệ có thể ảnh hưởng đến nhiều quyết định khác nhau bao gồm lựa chọn chỉ mục, hoạt động tìm kiếm so với quét, thực thi song song so với nối tiếp, lựa chọn thuật toán tham gia, lựa chọn tham gia vật lý bên trong so với bên ngoài (ví dụ:xây dựng so với đầu dò), tạo ống chỉ, tra cứu dấu trang so với truy cập toàn bộ theo nhóm hoặc bảng heap, lựa chọn tổng hợp luồng hoặc băm và việc sửa đổi dữ liệu có sử dụng gói rộng hay hẹp hay không. "
Cũng có những người khác, chẳng hạn như cấp bộ nhớ quá lớn hoặc quá nhỏ. Anh ấy tiếp tục mô tả một số nguyên nhân phổ biến hơn của các ước tính sai, nhưng nguyên nhân chính trong trường hợp này không có trong danh sách của anh ấy:các ước tính. Bởi vì chúng tôi đang sử dụng một biến cục bộ để thay đổi int
đến tham số cho một date
cục bộ duy nhất , SQL Server không biết giá trị sẽ là bao nhiêu, vì vậy nó đưa ra các phỏng đoán chuẩn hóa về số lượng dựa trên toàn bộ bảng.
Ở trên, chúng tôi thấy rằng ước tính cho cách tiếp cận được đề xuất của tôi là 5.170 hàng. Bây giờ, chúng ta biết rằng với một vị từ bất đẳng thức và với SQL Server không biết các giá trị tham số, nó sẽ đoán 30% của bảng. 31,645 * 0.3
không phải là 5,170. Cũng không phải là 31,465 * 0.3 * 0.3
, khi chúng ta nhớ rằng thực sự có hai vị từ hoạt động dựa trên cùng một cột. Vậy giá trị 5.170 này đến từ đâu?
Như Paul White mô tả trong bài đăng của anh ấy, "Ước tính số lượng cho nhiều dự đoán", công cụ ước tính số lượng mới trong SQL Server 2014 sử dụng hàm dự phòng theo cấp số nhân, vì vậy nó nhân số hàng của bảng (31,465) với độ chọn lọc của vị từ đầu tiên (0,3) , rồi nhân nó với căn bậc hai tính chọn lọc của vị từ thứ hai (~ 0,547723).
31,645 * (0,3) * SQRT (0,3) ~ =5,170.227Vì vậy, bây giờ chúng ta có thể thấy SQL Server đưa ra ước tính của nó ở đâu; chúng tôi có thể sử dụng một số phương pháp nào để giải quyết vấn đề này?
- Chuyển các tham số ngày. Khi có thể, bạn có thể thay đổi ứng dụng để ứng dụng chuyển các tham số ngày phù hợp thay vì các tham số số nguyên riêng biệt.
- Sử dụng quy trình trình bao bọc. Một biến thể trên phương pháp số 1 - ví dụ:nếu bạn không thể thay đổi ứng dụng - sẽ là tạo một thủ tục được lưu trữ thứ hai chấp nhận các tham số ngày đã xây dựng từ phương pháp đầu tiên.
- Sử dụng
OPTION (RECOMPILE)
. Với chi phí biên dịch nhỏ mỗi khi truy vấn được chạy, điều này buộc SQL Server phải tối ưu hóa dựa trên các giá trị được hiển thị mỗi lần, thay vì tối ưu hóa một kế hoạch duy nhất cho các giá trị tham số không xác định, đầu tiên hoặc trung bình. (Để tìm hiểu kỹ về chủ đề này, hãy xem "Nhúng tham số, nhúng và các tùy chọn RECOMPILE của Paul White."
- Sử dụng SQL động. Có SQL động chấp nhận
date
được xây dựng biến buộc phải tham số hóa thích hợp (giống như thể bạn đã gọi một thủ tục được lưu trữ vớidate
), nhưng nó hơi xấu và khó duy trì hơn.
- Lộn xộn với các gợi ý và cờ theo dõi. Paul White nói về một số điều này trong bài đăng nói trên.
Tôi sẽ không gợi ý rằng đây là một danh sách đầy đủ và tôi sẽ không nhắc lại lời khuyên của Paul về các gợi ý hoặc cờ theo dõi, vì vậy tôi sẽ chỉ tập trung vào việc chỉ ra cách bốn phương pháp tiếp cận đầu tiên có thể giảm thiểu vấn đề với các ước tính xấu .
1. Tham số ngày
CREATE PROCEDURE dbo.MonthlyReport_TwoDates @Start date, @End date AS BEGIN SET NOCOUNT ON; SELECT /* Two Dates */ DateColumn FROM dbo.DateEntries WHERE DateColumn >= @Start AND DateColumn < @End; END GO
2. Quy trình gói
CREATE PROCEDURE dbo.MonthlyReport_WrapperTarget @Start date, @End date AS BEGIN SET NOCOUNT ON; SELECT /* Wrapper */ DateColumn FROM dbo.DateEntries WHERE DateColumn >= @Start AND DateColumn < @End; END GO CREATE PROCEDURE dbo.MonthlyReport_WrapperSource @Year int, @Month int AS BEGIN SET NOCOUNT ON; DECLARE @Start date = DATEFROMPARTS(@Year, @Month, 1); DECLARE @End date = DATEADD(MONTH, 1, @Start); EXEC dbo.MonthlyReport_WrapperTarget @Start = @Start, @End = @End; END GO
3. TÙY CHỌN (THU HỒI)
CREATE PROCEDURE dbo.MonthlyReport_Recompile @Year int, @Month int AS BEGIN SET NOCOUNT ON; DECLARE @Start date = DATEFROMPARTS(@Year, @Month, 1); DECLARE @End date = DATEADD(MONTH, 1, @Start); SELECT /* Recompile */ DateColumn FROM dbo.DateEntries WHERE DateColumn >= @Start AND DateColumn < @End OPTION (RECOMPILE); END GO
4. SQL động
CREATE PROCEDURE dbo.MonthlyReport_DynamicSQL @Year int, @Month int AS BEGIN SET NOCOUNT ON; DECLARE @Start date = DATEFROMPARTS(@Year, @Month, 1); DECLARE @End date = DATEADD(MONTH, 1, @Start); DECLARE @sql nvarchar(max) = N'SELECT /* Dynamic SQL */ DateColumn FROM dbo.DateEntries WHERE DateColumn >= @Start AND DateColumn < @End;'; EXEC sys.sp_executesql @sql, N'@Start date, @End date', @Start, @End; END GO
Các bài kiểm tra
Với bốn bộ thủ tục đã có, thật dễ dàng để xây dựng các bài kiểm tra sẽ hiển thị cho tôi các kế hoạch và ước tính SQL Server thu được. Vì một số tháng bận hơn những tháng khác, tôi đã chọn ba tháng khác nhau và thực hiện tất cả các tháng đó nhiều lần.
DECLARE @Year int = 2012, @Month int = 7; -- 385 rows DECLARE @Start date = DATEFROMPARTS(@Year, @Month, 1); DECLARE @End date = DATEADD(MONTH, 1, @Start); EXEC dbo.MonthlyReport_Original @Year = @Year, @Month = @Month; EXEC dbo.MonthlyReport_TwoDates @Start = @Start, @End = @End; EXEC dbo.MonthlyReport_WrapperSource @Year = @Year, @Month = @Month; EXEC dbo.MonthlyReport_Recompile @Year = @Year, @Month = @Month; EXEC dbo.MonthlyReport_DynamicSQL @Year = @Year, @Month = @Month; /* repeat for @Year = 2011, @Month = 9 -- 157 rows */ /* repeat for @Year = 2014, @Month = 4 -- 2,115 rows */
Kết quả? Mọi kế hoạch đều mang lại Tìm kiếm Chỉ mục giống nhau, nhưng các ước tính chỉ đúng trên cả ba phạm vi ngày trong OPTION (RECOMPILE)
phiên bản. Phần còn lại tiếp tục sử dụng các ước tính thu được từ tập hợp thông số đầu tiên (tháng 7 năm 2012) và do đó, trong khi chúng nhận được ước tính tốt hơn cho đầu tiên thực thi, ước tính đó sẽ không nhất thiết phải tốt hơn cho tiếp theo thực thi sử dụng các tham số khác nhau (một trường hợp cổ điển, sách giáo khoa về đánh giá tham số):
Lưu ý rằng ở trên không phải là đầu ra * chính xác * từ SQL Sentry Plan Explorer - ví dụ:tôi đã xóa các hàng cây câu lệnh hiển thị các lệnh gọi thủ tục được lưu trữ bên ngoài và khai báo tham số.
Bạn sẽ tùy thuộc vào việc xác định xem chiến thuật biên dịch mọi lúc là tốt nhất cho bạn, hoặc liệu bạn có cần phải "sửa chữa" bất cứ điều gì ngay từ đầu hay không. Ở đây, chúng tôi đã kết thúc với các kế hoạch giống nhau và không có sự khác biệt đáng chú ý nào về số liệu hiệu suất thời gian chạy. Nhưng trên các bảng lớn hơn, với sự phân bố dữ liệu bị lệch nhiều hơn và sự khác biệt lớn hơn trong các giá trị vị từ (ví dụ:hãy xem xét một báo cáo có thể bao gồm một tuần, một năm và bất kỳ thứ gì ở giữa), nó có thể đáng để điều tra. Và lưu ý rằng bạn có thể kết hợp các phương pháp tại đây - ví dụ:bạn có thể chuyển sang thông số ngày thích hợp * và * thêm OPTION (RECOMPILE)
, nếu bạn muốn.
Kết luận
Trong trường hợp cụ thể này, đó là một sự đơn giản hóa có chủ đích, nỗ lực để có được các ước tính chính xác đã không thực sự thành công - chúng tôi không có một kế hoạch khác và hiệu suất thời gian chạy là tương đương. Tuy nhiên, chắc chắn có những trường hợp khác, nơi điều này sẽ tạo ra sự khác biệt và điều quan trọng là phải nhận ra sự chênh lệch ước tính và xác định xem liệu nó có thể trở thành vấn đề khi dữ liệu của bạn phát triển và / hoặc phân phối của bạn bị lệch. Thật không may, không có câu trả lời đen hoặc trắng, vì nhiều biến số sẽ ảnh hưởng đến việc liệu chi phí biên dịch có hợp lý hay không - như với nhiều trường hợp, IT DEPENDS ™ …