Giả sử "chi phí" là về mặt thời gian (mặc dù không chắc nó có thể là gì khác về ;-), thì ít nhất bạn sẽ có thể hiểu được điều đó bằng cách làm như sau:
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases'; -- replace with your proc
SET STATISTICS TIME OFF;
Mục đầu tiên được báo cáo trong tab "Tin nhắn" phải là:
Thời gian phân tích và biên dịch SQL Server:
Tôi sẽ chạy nó ít nhất 10 lần và trung bình cả mili giây "CPU" và "Đã qua".
Lý tưởng nhất là bạn sẽ chạy cái này trong Sản xuất để bạn có thể ước tính thời gian thực, nhưng hiếm khi mọi người được phép xóa bộ nhớ cache của kế hoạch trong Sản xuất. May mắn thay, bắt đầu từ SQL Server 2008, có thể xóa một kế hoạch cụ thể khỏi bộ đệm. Trong trường hợp bạn có thể làm như sau:
DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
SELECT DISTINCT stat.plan_handle
FROM sys.dm_exec_query_stats stat
CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
+ CONVERT(NVARCHAR(130), cte.plan_handle, 1)
+ N');'
+ NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases' -- replace with your proc
SET STATISTICS TIME OFF;
Tuy nhiên, tùy thuộc vào sự thay đổi của các giá trị được truyền cho (các) tham số gây ra kế hoạch lưu trữ "xấu", có một phương pháp khác để xem xét đó là một trung gian giữa OPTION(RECOMPILE)
vàOPTION(OPTIMIZE FOR UNKNOWN)
: SQL động. Vâng, tôi đã nói nó. Và tôi thậm chí có nghĩa là SQL động không tham số. Đây là lý do tại sao.
Bạn rõ ràng có dữ liệu có phân phối không đồng đều, ít nhất là về một hoặc nhiều giá trị tham số đầu vào. Nhược điểm của các tùy chọn được đề cập là:
OPTION(RECOMPILE)
sẽ tạo ra một kế hoạch cho mỗi lần thực hiện và bạn sẽ không bao giờ có thể hưởng lợi từ bất kỳ việc sử dụng lại kế hoạch nào , ngay cả khi các giá trị tham số được truyền lại giống hệt với (các) lần chạy trước. Đối với các procs được gọi thường xuyên - cứ sau vài giây hoặc thường xuyên hơn - điều này sẽ cứu bạn khỏi tình huống khủng khiếp thường xuyên, nhưng vẫn khiến bạn rơi vào tình huống không phải lúc nào cũng tuyệt vời.
OPTION(OPTIMIZE FOR (@Param = value))
sẽ tạo ra một kế hoạch dựa trên giá trị cụ thể đó, có thể giúp một số trường hợp nhưng vẫn để bạn mở cho vấn đề hiện tại.
OPTION(OPTIMIZE FOR UNKNOWN)
sẽ tạo ra một kế hoạch dựa trên số tiền cho một phân phối trung bình, điều này sẽ giúp một số truy vấn nhưng làm tổn thương những người khác. Điều này sẽ giống như tùy chọn sử dụng các biến cục bộ.
Tuy nhiên, SQL động, khi được thực hiện một cách chính xác , sẽ cho phép các giá trị khác nhau được truyền vào để có các gói truy vấn riêng biệt rất lý tưởng (cũng như nhiều như chúng sẽ có). Chi phí chính ở đây là khi sự đa dạng của các giá trị được truyền vào tăng lên, số lượng kế hoạch thực hiện trong bộ đệm tăng lên và chúng chiếm bộ nhớ. Các chi phí nhỏ là:
cần xác thực các tham số chuỗi để ngăn chặn SQL Tiêm
có thể cần phải thiết lập Người dùng dựa trên Chứng chỉ và Chứng chỉ để duy trì sự trừu tượng hóa bảo mật lý tưởng vì Dynamic SQL yêu cầu quyền truy cập bảng trực tiếp.
Vì vậy, đây là cách tôi đã quản lý tình huống này khi tôi có các procs được gọi nhiều hơn một lần mỗi giây và nhấn nhiều bảng, mỗi bảng có hàng triệu hàng. Tôi đã thử OPTION(RECOMPILE)
nhưng điều này tỏ ra quá bất lợi cho quá trình trong 99% trường hợp không có thông số về vấn đề kế hoạch đánh hơi / bộ nhớ cache xấu. Và xin lưu ý rằng một trong những procs này có khoảng 15 truy vấn trong đó và chỉ 3 - 5 trong số chúng được chuyển đổi thành SQL động như được mô tả ở đây; SQL động không được sử dụng trừ khi cần thiết cho một truy vấn cụ thể.
Nếu có nhiều tham số đầu vào cho thủ tục được lưu trữ, hãy tìm ra tham số nào được sử dụng với các cột có phân phối dữ liệu chênh lệch cao (và do đó gây ra sự cố này) và tham số nào được sử dụng với các cột có phân phối đều hơn (và không nên gây ra vấn đề này).
Xây dựng chuỗi SQL động bằng cách sử dụng các tham số cho các tham số đầu vào Proc được liên kết với các cột được phân bổ đều. Việc tham số hóa này giúp giảm kết quả tăng trong kế hoạch thực hiện trong bộ đệm liên quan đến truy vấn này.
Đối với các tham số còn lại được liên kết với các bản phân phối có độ đa dạng cao, chúng nên được nối vào SQL động dưới dạng giá trị bằng chữ. Vì một truy vấn duy nhất được xác định bởi bất kỳ thay đổi nào đối với văn bản truy vấn, nên có WHERE StatusID = 1
một truy vấn khác và do đó, kế hoạch truy vấn khác nhau, hơn là có WHERE StatusID = 2
.
Nếu bất kỳ tham số đầu vào Proc nào được nối vào văn bản của truy vấn là các chuỗi, thì chúng cần phải được xác thực để bảo vệ chống lại SQL Injection (mặc dù điều này ít xảy ra nếu các chuỗi được truyền vào được tạo bởi ứng dụng và không phải là người dùng, nhưng vẫn còn). Ít nhất là làm REPLACE(@Param, '''', '''''')
để đảm bảo rằng dấu ngoặc đơn trở thành thoát dấu ngoặc đơn.
Nếu cần, hãy tạo Chứng chỉ sẽ được sử dụng để tạo Người dùng và ký quy trình được lưu trữ để các quyền của bảng trực tiếp sẽ chỉ được cấp cho Người dùng dựa trên Chứng chỉ mới và không [public]
hoặc cho Người dùng không có quyền đó. .
Ví dụ Proc:
CREATE PROCEDURE MySchema.MyProc
(
@Param1 INT,
@Param2 DATETIME,
@Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'
SELECT tab.Field1, tab.Field2, ...
FROM MySchema.SomeTable tab
WHERE tab.Field3 = @P1
AND tab.Field8 >= CONVERT(DATETIME, ''' +
CONVERT(NVARCHAR(50), @Param2, 121) +
N''')
AND tab.Field2 LIKE N''' +
REPLACE(@Param3, N'''', N'''''') +
N'%'';';
EXEC sp_executesql
@SQL,
N'@P1 INT',
@P1 = @Param1;