Làm thế nào để đo lường hoặc tìm chi phí tạo kế hoạch truy vấn?


18

Tôi có một trường hợp điển hình trong đó việc đánh hơi tham số khiến kế hoạch thực thi "xấu" rơi vào bộ đệm của kế hoạch, khiến các lần thực hiện tiếp theo của thủ tục được lưu trữ của tôi rất chậm. Tôi có thể "giải quyết" vấn đề này với các biến cục bộ OPTIMIZE FOR ... UNKNOWN, và OPTION(RECOMPILE). Tuy nhiên, tôi cũng có thể đi sâu vào truy vấn và cố gắng tối ưu hóa nó.

Tôi đang cố gắng xác định xem tôi có nên : đưa ra thời gian hạn chế để khắc phục sự cố tôi muốn biết chi phí của việc không thực hiện. Như tôi thấy, nếu tôi chỉ sử dụng OPTION(RECOMPILE), hiệu ứng ròng là kế hoạch truy vấn được tạo lại mỗi khi truy vấn được chạy. Vì vậy, tôi nghĩ rằng tôi cần phải biết:

Làm thế nào để tìm ra chi phí tạo ra một kế hoạch truy vấn là gì?

Để trả lời câu hỏi của riêng tôi, tôi đã Googled (ví dụ với truy vấn này ) và tôi đã xem qua tài liệu về các cột cho dm_exec_query_statsDMV . Tôi cũng đã kiểm tra cửa sổ đầu ra trong SSMS cho "Kế hoạch truy vấn thực tế" để tìm thông tin này. Cuối cùng, tôi đã tìm kiếm DBA.SE . Không ai trong số họ dẫn đến một câu trả lời.

Bất cứ ai có thể cho tôi biết? Có thể tìm thấy hoặc đo thời gian cần thiết để tạo kế hoạch?


5
Tôi khuyên bạn nên lấy một bản sao của Trình tối ưu hóa truy vấn SQL Server bên trong của Benjamin Nevarez . Nó miễn phí. Chương 5 'Quy trình tối ưu hóa' có thể giúp bạn tính toán thời gian biên dịch cho truy vấn của mình. Ít nhất, đó là thông tin về những gì trình tối ưu hóa trải qua để tạo ra một kế hoạch truy vấn.
Mark Sinkinson

Câu trả lời:


18

Làm thế nào để tìm ra chi phí tạo ra một kế hoạch truy vấn là gì?

Bạn có thể xem các thuộc tính của nút gốc trong kế hoạch truy vấn, ví dụ:

Chiết xuất thuộc tính gốc
(ảnh chụp màn hình từ miễn phí Sentry One Plan Explorer )

Thông tin này cũng có sẵn bằng cách truy vấn bộ đệm của kế hoạch, ví dụ: sử dụng truy vấn dựa trên các mối quan hệ sau:

WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    CompileTime = c.value('(QueryPlan/@CompileTime)[1]', 'int'),
    CompileCPU = c.value('(QueryPlan/@CompileCPU)[1]', 'int'),
    CompileMemory = c.value('(QueryPlan/@CompileMemory)[1]', 'int'),
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_query_plan(CP.plan_handle) AS QP
CROSS APPLY sys.dm_exec_sql_text(CP.plan_handle) AS ST
CROSS APPLY QP.query_plan.nodes('ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS N(c);

Kết quả mảnh

Để biết cách xử lý đầy đủ các tùy chọn bạn có để xử lý các loại truy vấn này, hãy xem bài viết được cập nhật gần đây của Erland Sommarskog .


4

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)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ể.

  1. 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).

  2. 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.

  3. Đố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 = 1một truy vấn khác và do đó, kế hoạch truy vấn khác nhau, hơn là có WHERE StatusID = 2.

  4. 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.

  5. 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;

Cảm ơn đã dành (khá nhiều) thời gian để trả lời! Tôi hơi nghi ngờ mặc dù về bit đầu tiên về cách lấy thời gian biên dịch, với điều kiện là nó thấp hơn 3 so với kết quả tôi nhận được bằng cách sử dụng phương pháp của @ PaulWhite . - Thứ hai trên bit SQL động rất thú vị (mặc dù nó cũng cần có thời gian để thực hiện; ít nhất là nhiều hơn là chỉ vỗ OPTIONvào truy vấn của tôi) và sẽ không làm tổn thương tôi quá nhiều vì sproc này được khai thác tốt trong các thử nghiệm tích hợp. - Trong mọi trường hợp: cảm ơn những hiểu biết của bạn!
Jeroen
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.