Trường = Tham số HOẶC Tham số IS Mô hình NULL


7

Tôi nhận thức được các vấn đề đánh hơi tham số liên quan đến các thủ tục được lưu trữ được viết bằng một vị từ như sau:

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    SELECT [Field] FROM [dbo].[Table]
    WHERE [Field] = @Parameter 
    OR @Parameter IS NULL;
END;

Tùy thuộc vào giá trị của tham số, vô hướng hoặc NULL trong lần thực hiện đầu tiên, một kế hoạch được lưu trong bộ nhớ cache có khả năng sẽ là tối ưu phụ cho giá trị ngược lại.

Giả sử [Trường] là vô hướng và chỉ mục phân cụm trên bảng. Những ưu và nhược điểm của các cách tiếp cận sau đây để viết (các) thủ tục được lưu trữ để hỗ trợ truy vấn:

Chọn điều kiện trong cùng một thủ tục lưu trữ

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    IF(@Parameter IS NOT NULL) BEGIN;
        SELECT [Field]
        FROM [dbo].[Table]
        WHERE [Field] = @Parameter;
    END;
    ELSE BEGIN;
        SELECT [Field]
        FROM [dbo].[Table];
    END;
END;

SQL động trong thủ tục được lưu trữ

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    DECLARE @sql NVARCHAR(MAX) = N'';
    SET @sql += N'SELECT [Field]'
    SET @sql += N'FROM [dbo].[Table]';

    IF(@Parameter IS NOT NULL) BEGIN;
        @sql += N'WHERE [Field] = @Parameter';
    END;

    SET @sql += N';';

    EXEC sp_executesql @sql N'@Parameter INT', @Parameter;
END;

Thủ tục lưu trữ riêng

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    SELECT [Field]
    FROM [dbo].[Table]
    WHERE [Field] = @Parameter;
END;

CREATE PROCEDURE [dbo].[GetAll] AS BEGIN;
    SELECT [Field]
    FROM [dbo].[Table];
END;

5
Tôi là một fan hâm mộ lớn của cách tiếp cận SQL động bởi vì bạn có thể dễ dàng tinh chỉnh mọi kết hợp tham số duy nhất nếu bạn phải. Xem blog.sentryone.com/aaronbertrand/ trộm
Aaron Bertrand

2
Cái đầu tiên có vẻ như bạn có thể gặp các vấn đề đánh hơi tham số nếu được biên dịch cho NULL Tôi tưởng tượng nhánh KHÔNG NULL sẽ bị kẹt với ước tính 1 hàng.
Martin Smith

@Martin Smith - Tôi cũng nghĩ như vậy, nhưng không phải vậy, câu hỏi này đi vào chi tiết về những gì sẽ thực sự xảy ra: dba.stackexchange.com/questions/185252/ .
M. Jacobson

@ M.Jacobson trả lời sao lưu những gì tôi nghĩ sẽ xảy ra. Tất cả các câu lệnh sẽ được biên dịch theo giá trị tham số đầu tiên được thông qua. Khi đó là NULL thì =NULLsẽ trả về 0 hàng. Mặc dù các ước tính hàng không thường được làm tròn lên tới 1
Martin Smith

@Martin Smith - Xin lỗi, tôi đã hiểu sai câu nói của bạn.
M. Jacobson

Câu trả lời:


5

Họ đều xuất sắc. Có thật không. Tất cả đều có cùng tác động là có hai gói trong bộ đệm, đó là những gì bạn muốn.

Khi bạn nhận được càng nhiều tham số, bạn sẽ thấy tùy chọn Dynamic SQL rõ ràng nhất, mặc dù nó có vẻ đáng sợ hơn đối với người mới bắt đầu.

Nếu đây là một chức năng tôi khuyên bạn nên tránh các tùy chọn đa câu lệnh, để QO có thể thực hiện công cụ của nó độc đáo hơn.


5

Nếu bạn đang xây dựng SQL Server một cách hợp lý gần đây, một tùy chọn khác có thể được xem xét là

SELECT [Field]
FROM   [dbo].[Table]
WHERE  [Field] = @Parameter
        OR @Parameter IS NULL
OPTION (RECOMPILE); 

Để có được một kế hoạch tối ưu cho giá trị thời gian chạy mỗi lần với chi phí biên dịch mỗi lần.

Tùy chọn "Điều kiện chọn" của bạn vẫn dễ bị đánh hơi thông số. Nếu thủ tục được thực hiện lần đầu tiên khi @Parameternull, thì nhánh có biến [Field] = @Parametervị ngữ sẽ ước tính 1 hàng (được làm tròn từ 0 dự kiến ​​cho một =NULLvị từ).

Trong ví dụ cụ thể trong câu hỏi của bạn, nơi bạn đang chọn một cột duy nhất và đó là cùng một cột mà bạn đang lọc bởi điều này không có khả năng đưa ra một vấn đề nhưng nó có thể làm trong các trường hợp khác.

ví dụ với ví dụ sau đây, cuộc gọi đầu tiên [dbo].[Get] 1nhận được 333.731 lượt đọc logic vì nó chọn một kế hoạch không phù hợp với tra cứu chính. Khi kế hoạch được xóa khỏi bộ đệm và được biên dịch lại với 1đầu tiên, số lần đọc logic giảm xuống còn 4.330

DROP TABLE IF EXISTS [Table]

GO

CREATE TABLE [Table]
(
[Field1]  INT INDEX IX,
[Field2]  INT,
[Field3]  INT,
);

INSERT INTO [Table]
SELECT TOP 1000000 CRYPT_GEN_RANDOM(1)%3, CRYPT_GEN_RANDOM(4), CRYPT_GEN_RANDOM(4)
FROM sys.all_objects o1, sys.all_objects o2

GO

CREATE OR ALTER PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    IF(@Parameter IS NOT NULL) BEGIN;
        SELECT *
        FROM [dbo].[Table]
        WHERE [Field1] = @Parameter;
    END;
    ELSE BEGIN;
        SELECT *
        FROM [dbo].[Table];
    END;
END;

GO

SET STATISTICS TIME ON
SET STATISTICS IO ON


EXEC [dbo].[Get] 

EXEC [dbo].[Get] 1;

declare @plan_handle varbinary(64) = (select plan_handle from sys.dm_exec_procedure_stats where object_id = object_id('[dbo].[Get]'));

--Remove the plan from the cache 
DBCC FREEPROCCACHE (@plan_handle);  

--Re-execute it with NOT NULL passed first
EXEC [dbo].[Get] 1;

2

Dựa trên các câu trả lời và nhận xét trước đó từ Aaron Bertrand, Martin Smith và Rob Farley. Tôi muốn tập hợp một danh sách pro / con cho mỗi cách tiếp cận, bao gồm cả cách tiếp cận bổ sung TÙY CHỌN (RECOMPILE):


Chọn điều kiện trong cùng một thủ tục lưu trữ

Từ phản hồi của Martin Smith:

Tùy chọn "Điều kiện chọn" của bạn vẫn dễ bị đánh hơi thông số. Nếu quy trình được thực hiện lần đầu tiên khi @Parameter là null thì nhánh có biến vị ngữ [Field] = @Parameter sẽ ước tính 1 hàng (được làm tròn từ 0 dự kiến ​​cho vị từ a = NULL).

  • Không có chi phí biên dịch lại.
  • Lập kế hoạch tái sử dụng bộ đệm cho mỗi câu lệnh và thủ tục được lưu trữ.
  • Các kế hoạch lưu trữ dễ bị tổn thương đối với các tham số đánh hơi ngay cả khi không có phương sai đáng kể trong tập kết quả khi @Parameter KHÔNG phải là NULL.
  • Không quy mô tốt về mặt hành chính khi số lượng tham số tăng lên.
  • Intellisense trên tất cả T-SQL.

SQL động trong thủ tục được lưu trữ

Từ Rob Farley:

Khi bạn nhận được càng nhiều tham số, bạn sẽ thấy tùy chọn Dynamic SQL rõ ràng nhất, mặc dù nó có vẻ đáng sợ hơn đối với người mới bắt đầu.

  • Không có chi phí biên dịch lại.
  • Lập kế hoạch tái sử dụng bộ đệm cho mỗi câu lệnh và thủ tục được lưu trữ.
  • Các gói được lưu trong bộ nhớ cache dễ bị tổn thương bởi các tham số chỉ đánh hơi khi có sự chênh lệch đáng kể trong tập kết quả khi @Parameter KHÔNG phải là NULL.
  • Cân tốt về mặt hành chính khi số lượng tham số tăng lên.
  • Không cung cấp Intellisense trên tất cả T-SQL.

Thủ tục lưu trữ riêng

  • Không có chi phí biên dịch lại.
  • Lập kế hoạch tái sử dụng bộ đệm cho mỗi câu lệnh và thủ tục được lưu trữ.
  • Các gói được lưu trong bộ nhớ cache dễ bị tổn thương bởi các tham số chỉ đánh hơi khi có sự chênh lệch đáng kể trong tập kết quả khi @Parameter KHÔNG phải là NULL.
  • Không quy mô tốt về mặt hành chính khi số lượng tham số tăng lên.
  • Intellisense trên tất cả T-SQL.

TÙY CHỌN (RECOMPILE)

Từ Martin Smith:

Để có được một kế hoạch tối ưu cho giá trị thời gian chạy mỗi lần với chi phí tổng hợp mỗi lần.

  • Chi phí CPU cho biên dịch lại.
  • Không có kế hoạch sử dụng lại bộ đệm cho các câu lệnh theo sau TÙY CHỌN (RECOMPILE), chỉ có các thủ tục được lưu trữ và các câu lệnh không có TÙY CHỌN (RECOMPILE).
  • Cân tốt về mặt hành chính khi số lượng tham số tăng lên.
  • Không dễ bị đánh hơi thông số.
  • Intellisense trên tất cả T-SQL.

Takeaway cá nhân của tôi

Nếu không có phương sai đáng kể trong tập kết quả với các giá trị vô hướng khác nhau của @Parameter, thì SQL động là công cụ hàng đầu, có ít chi phí hệ thống nhất và chỉ kém hơn một chút so với chi phí quản trị so với TÙY CHỌN (RECOMPILE). Trong các kịch bản phức tạp hơn, khi phương sai trong giá trị tham số có thể gây ra thay đổi đáng kể trong các tập kết quả, sử dụng SQL động với sự bao gồm có điều kiện hoặc loại trừ TÙY CHỌN (RECOMPILE) sẽ là hiệu suất tổng thể tốt nhất. Đây là đường dẫn đến bài viết của Aaron Bertrand mô tả chi tiết cách tiếp cận: https://bloss.sentryone.com/aaronbertrand/backtobasics-updated-kove-sink-example/

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.