Có những tính năng mà nhiều người trong chúng ta né tránh, như con trỏ, trình kích hoạt và SQL động. Không có câu hỏi nào mà họ có các trường hợp sử dụng của mình, nhưng khi chúng ta thấy một trình kích hoạt với con trỏ bên trong SQL động, nó có thể khiến chúng ta co rúm người lại (gấp ba lần).
Hướng dẫn kế hoạch và sp_prepare ở trong một chiếc thuyền tương tự:nếu bạn thấy tôi sử dụng một trong số chúng, bạn sẽ nhướng mày; nếu bạn thấy tôi sử dụng chúng cùng nhau, bạn có thể kiểm tra nhiệt độ của tôi. Tuy nhiên, cũng như với con trỏ, trình kích hoạt và SQL động, chúng có các trường hợp sử dụng của chúng. Và gần đây tôi đã gặp một tình huống trong đó sử dụng chúng cùng nhau sẽ có lợi.
Bối cảnh
Chúng tôi có rất nhiều dữ liệu. Và rất nhiều ứng dụng chạy ngược lại dữ liệu đó. Một số ứng dụng khó hoặc không thể thay đổi, đặc biệt là các ứng dụng không có sẵn từ bên thứ ba. Vì vậy, khi ứng dụng đã biên dịch của họ gửi các truy vấn đặc biệt tới SQL Server, đặc biệt là dưới dạng một câu lệnh đã chuẩn bị sẵn và khi chúng tôi không có quyền tự do thêm hoặc thay đổi chỉ mục, một số cơ hội điều chỉnh sẽ không có sẵn.
Trong trường hợp này, chúng ta có một bảng với vài triệu hàng. Một phiên bản đơn giản hóa và được làm sạch:
CREATE TABLE dbo.TheThings ( ThingID bigint NOT NULL, TypeID uniqueidentifier NOT NULL, dt1 datetime NOT NULL DEFAULT sysutcdatetime(), dt2 datetime NOT NULL DEFAULT sysutcdatetime(), dt3 datetime NOT NULL DEFAULT sysutcdatetime(), CONSTRAINT PK_TheThings PRIMARY KEY (ThingID) ); CREATE INDEX ix_type ON dbo.TheThings(TypeID); SET NOCOUNT ON; GO DECLARE @guid1 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4', @guid2 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; INSERT dbo.TheThings(ThingID, TypeID) SELECT TOP (1000) 1000 + ROW_NUMBER() OVER (ORDER BY name), @guid1 FROM sys.all_columns; INSERT dbo.TheThings(ThingID, TypeID) SELECT TOP (1) 2500, @guid2 FROM sys.all_columns; INSERT dbo.TheThings(ThingID, TypeID) SELECT TOP (1000) 3000 + ROW_NUMBER() OVER (ORDER BY name), @guid1 FROM sys.all_columns;
Câu lệnh đã chuẩn bị từ ứng dụng trông như thế này (như được thấy trong bộ nhớ cache của gói):
(@P0 varchar(8000))SELECT * FROM dbo.TheThings WHERE TypeID = @P0
Vấn đề là, đối với một số giá trị của TypeID
, sẽ có hàng ngàn hàng. Đối với các giá trị khác, sẽ có ít hơn 10. Nếu kế hoạch sai được chọn (và sử dụng lại) dựa trên một loại tham số, điều này có thể gây rắc rối cho những người khác. Đối với truy vấn truy xuất một số ít hàng, chúng tôi muốn tìm kiếm chỉ mục với các tìm kiếm để truy xuất thêm các cột không được bao phủ, nhưng đối với truy vấn trả về 700K hàng, chúng tôi chỉ muốn quét chỉ mục theo nhóm. (Lý tưởng nhất là chỉ mục sẽ bao gồm, nhưng tùy chọn này không có trong các thẻ lần này.)
Trong thực tế, ứng dụng luôn nhận được biến thể quét, mặc dù đó là biến thể cần thiết khoảng 1% thời gian. 99% các truy vấn đang sử dụng quét 2 triệu hàng khi họ có thể sử dụng tìm kiếm + 4 hoặc 5 tìm kiếm.
Chúng tôi có thể dễ dàng tạo lại điều này trong Management Studio bằng cách chạy truy vấn sau:
DBCC FREEPROCCACHE; DECLARE @P0 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4'; SELECT * FROM dbo.TheThings WHERE TypeID = @P0; GO DBCC FREEPROCCACHE; DECLARE @P0 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; SELECT * FROM dbo.TheThings WHERE TypeID = @P0; GO
Các kế hoạch trở lại như thế này:
Ước tính trong cả hai trường hợp là 1.000 hàng; các cảnh báo ở bên phải là do I / O còn sót lại.
Làm cách nào chúng tôi có thể đảm bảo rằng truy vấn đã đưa ra lựa chọn đúng tùy thuộc vào tham số? Chúng tôi cần phải biên dịch lại nó mà không cần thêm gợi ý vào truy vấn, bật cờ theo dõi hoặc thay đổi cài đặt cơ sở dữ liệu.
Nếu tôi chạy các truy vấn một cách độc lập bằng cách sử dụng OPTION (RECOMPILE)
, Tôi sẽ tìm kiếm khi thích hợp:
DBCC FREEPROCCACHE; DECLARE @guid1 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4', @guid2 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; SELECT * FROM dbo.TheThings WHERE TypeID = @guid1 OPTION (RECOMPILE); SELECT * FROM dbo.TheThings WHERE TypeID = @guid2 OPTION (RECOMPILE);
Với RECOMPILE, chúng tôi nhận được các ước tính chính xác hơn và tìm kiếm khi chúng tôi cần.
Tuy nhiên, một lần nữa, chúng tôi không thể thêm gợi ý vào truy vấn trực tiếp.
Hãy thử hướng dẫn kế hoạch
Rất nhiều người cảnh báo chống lại hướng dẫn kế hoạch, nhưng chúng tôi đã ở một góc ở đây. Chúng tôi chắc chắn muốn thay đổi truy vấn hoặc các chỉ mục, nếu có thể. Nhưng đây có thể là điều tốt nhất tiếp theo.
EXEC sys.sp_create_plan_guide @name = N'TheThingGuide', @stmt = N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0', @type = N'SQL', @params = N'@P0 varchar(8000)', @hints = N'OPTION (RECOMPILE)';
Có vẻ đơn giản; thử nghiệm nó là vấn đề. Làm cách nào để chúng tôi mô phỏng một tuyên bố đã chuẩn bị trong Management Studio? Làm cách nào để chúng tôi có thể chắc chắn rằng ứng dụng đang nhận được kế hoạch được hướng dẫn và nó rõ ràng là do có hướng dẫn về kế hoạch?
Nếu chúng tôi cố gắng mô phỏng truy vấn này trong SSMS, thì điều này được coi là một câu lệnh đặc biệt, không phải là một câu lệnh chuẩn bị và tôi không thể hiểu câu này để xem hướng dẫn gói:
DECLARE @P0 varchar(8000) = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; -- also tried uniqueidentifier SELECT * FROM dbo.TheThings WHERE TypeID = @P0
SQL động cũng không hoạt động (điều này cũng được coi là một câu lệnh đặc biệt):
DECLARE @sql nvarchar(max) = N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0', @params nvarchar(max) = N'@P0 varchar(8000)', -- also tried uniqueidentifier @P0 varchar(8000) = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; EXEC sys.sp_executesql @sql, @params, @P0;
Và tôi không thể làm điều này, bởi vì nó cũng không nhận được hướng dẫn kế hoạch (tham số hóa sẽ tiếp quản ở đây và tôi không có quyền tự do thay đổi cài đặt cơ sở dữ liệu, ngay cả khi điều này được coi như một tuyên bố đã chuẩn bị) :
SELECT * FROM TheThings WHERE TypeID = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F';
Tôi không thể kiểm tra bộ đệm ẩn của gói cho các truy vấn chạy từ ứng dụng, vì gói được lưu trong bộ nhớ cache không cho biết bất kỳ điều gì về việc sử dụng hướng dẫn gói (SSMS đưa thông tin đó vào XML cho bạn khi bạn tạo một gói thực tế). Và nếu truy vấn thực sự quan sát gợi ý RECOMPILE mà tôi đang chuyển đến hướng dẫn kế hoạch, thì làm sao tôi có thể thấy bất kỳ bằng chứng nào trong bộ nhớ cache của kế hoạch?
Hãy thử sp_prepare
Tôi đã sử dụng sp_prepare ít hơn trong sự nghiệp của mình so với hướng dẫn kế hoạch và tôi không khuyên bạn nên sử dụng sp_prepare cho mã ứng dụng. (Như Erik Darling đã chỉ ra, ước tính có thể được lấy từ vectơ mật độ, không phải từ đánh giá tham số.)
Trong trường hợp của tôi, tôi không muốn sử dụng nó vì lý do hiệu suất, tôi muốn sử dụng nó (cùng với sp_execute) để mô phỏng câu lệnh đã chuẩn bị từ ứng dụng.
DECLARE @o int; EXEC sys.sp_prepare @o OUTPUT, N'@P0 varchar(8000)', N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0'; EXEC sys.sp_execute @o, 'EE81197A-B2EA-41F4-882E-4A5979ACACE4'; -- PK scan EXEC sys.sp_execute @o, 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; -- IX seek + lookup
SSMS cho chúng ta biết hướng dẫn gói được sử dụng trong cả hai trường hợp.
Bạn sẽ không thể kiểm tra bộ nhớ cache của kế hoạch cho các kết quả này vì quá trình biên dịch lại. Nhưng trong trường hợp như của tôi, bạn sẽ có thể thấy các tác động trong việc theo dõi, kiểm tra rõ ràng qua Sự kiện mở rộng hoặc quan sát sự thuyên giảm của triệu chứng khiến bạn điều tra truy vấn này ngay từ đầu (chỉ cần lưu ý rằng thời gian chạy trung bình, truy vấn thống kê, v.v. có thể bị ảnh hưởng bởi việc biên dịch bổ sung).
Kết luận
Đây là một trường hợp mà hướng dẫn kế hoạch có lợi và sp_prepare hữu ích trong việc xác nhận rằng nó sẽ hoạt động cho ứng dụng. Những thứ này thường không hữu ích và ít khi đi cùng nhau, nhưng đối với tôi, đó là một sự kết hợp thú vị. Ngay cả khi không có hướng dẫn gói, nếu bạn muốn sử dụng SSMS để mô phỏng một ứng dụng gửi các báo cáo đã chuẩn bị sẵn, sp_prepare là bạn của bạn. (Cũng xem sp_prepexec, đây có thể là một phím tắt nếu bạn không cố gắng xác thực hai gói khác nhau cho cùng một truy vấn.)
Lưu ý rằng bài tập này không nhất thiết lúc nào cũng để đạt được hiệu suất tốt hơn - nó là để làm phẳng phương sai hiệu suất. Bản biên dịch rõ ràng là không miễn phí, nhưng tôi sẽ trả một khoản phạt nhỏ để 99% truy vấn của tôi được thực thi trong 250 mili giây và 1% thực thi trong 5 giây, thay vì bị mắc kẹt với một kế hoạch hoàn toàn khủng khiếp cho 99% truy vấn hoặc 1% trong số các truy vấn.