Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Chỉ mục được lọc và Tham số bắt buộc (redux)

Sau khi viết blog về cách các chỉ mục được lọc có thể mạnh hơn và gần đây hơn về cách chúng có thể trở nên vô dụng bằng cách tham số hóa bắt buộc, tôi đang xem lại chủ đề chỉ mục / tham số đã lọc. Gần đây, một giải pháp dường như quá đơn giản đã được đưa ra tại nơi làm việc và tôi phải chia sẻ.

Lấy ví dụ sau, trong đó chúng ta có một cơ sở dữ liệu bán hàng chứa một bảng các đơn đặt hàng. Đôi khi chúng ta chỉ muốn có một danh sách (hoặc một số lượng) chỉ các đơn hàng chưa được vận chuyển - theo thời gian, (hy vọng!) Đại diện cho một tỷ lệ phần trăm nhỏ hơn và nhỏ hơn của bảng tổng thể:

CREATE DATABASE Sales;
GO
USE Sales;
GO
 
-- simplified, obviously:
CREATE TABLE dbo.Orders
(
    OrderID   int IDENTITY(1,1) PRIMARY KEY,
    OrderDate datetime  NOT NULL,
    filler    char(500) NOT NULL DEFAULT '',
    IsShipped bit       NOT NULL DEFAULT 0
);
GO
 
-- let's put some data in there; 7,000 shipped orders, and 50 unshipped:
 
INSERT dbo.Orders(OrderDate, IsShipped)
  -- random dates over two years
  SELECT TOP (7000) DATEADD(DAY, ABS(object_id % 730), '20171101'), 1 
  FROM sys.all_columns
UNION ALL 
  -- random dates from this month
  SELECT TOP (50)   DATEADD(DAY, ABS(object_id % 30),  '20191201'), 0 
  FROM sys.all_columns;

Trong trường hợp này, có thể có ý nghĩa khi tạo một chỉ mục được lọc như thế này (giúp xử lý nhanh chóng bất kỳ truy vấn nào đang cố gắng đạt được những đơn hàng chưa được vận chuyển đó):

CREATE INDEX ix_OrdersNotShipped 
  ON dbo.Orders(IsShipped, OrderDate) 
  WHERE IsShipped = 0;

Chúng tôi có thể chạy một truy vấn nhanh như thế này để xem nó sử dụng chỉ mục đã lọc như thế nào:

SELECT OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0;

Kế hoạch thực hiện khá đơn giản, nhưng có một cảnh báo về UnmishedIndexes:

Tên của cảnh báo hơi gây hiểu lầm - cuối cùng trình tối ưu hóa đã có thể sử dụng chỉ mục, nhưng đang gợi ý rằng nó sẽ "tốt hơn" nếu không có tham số (mà chúng tôi không sử dụng rõ ràng), mặc dù câu lệnh trông giống như nó đã được tham số hóa:

Nếu bạn thực sự muốn, bạn có thể loại bỏ cảnh báo, không có sự khác biệt về hiệu suất thực tế (nó sẽ chỉ là thẩm mỹ). Một cách là thêm một vị từ không tác động, như AND (1 > 0) :

SELECT wadd = OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0 AND (1 > 0);

Một cách khác (có thể phổ biến hơn) là thêm OPTION (RECOMPILE) :

SELECT wrecomp = OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0 OPTION (RECOMPILE);

Cả hai tùy chọn này đều mang lại cùng một kế hoạch (tìm kiếm không có cảnh báo):

Càng xa càng tốt; chỉ mục đã lọc của chúng tôi đang được sử dụng (như mong đợi). Tất nhiên, đây không phải là thủ thuật duy nhất; xem các nhận xét bên dưới cho những người khác mà người đọc đã gửi.

Sau đó, phức tạp

Bởi vì cơ sở dữ liệu phải tuân theo một số lượng lớn các truy vấn đặc biệt, ai đó đã bật tham số hóa bắt buộc, cố gắng giảm quá trình biên dịch và loại bỏ các kế hoạch sử dụng một lần và thấp làm ô nhiễm bộ nhớ cache của kế hoạch:

ALTER DATABASE Sales SET PARAMETERIZATION FORCED;

Bây giờ truy vấn ban đầu của chúng tôi không thể sử dụng chỉ mục đã lọc; nó buộc phải quét chỉ mục được phân cụm:

SELECT OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0;

Cảnh báo về các chỉ mục chưa được so khớp trả về và chúng tôi nhận được cảnh báo mới về I / O còn lại. Lưu ý rằng câu lệnh được tham số hóa, nhưng nó trông hơi khác một chút:

Đây là do thiết kế, vì toàn bộ mục đích của tham số hóa bắt buộc là tham số hóa các truy vấn như thế này. Nhưng nó đánh bại mục đích của chỉ mục đã lọc của chúng tôi, vì điều đó có nghĩa là hỗ trợ một giá trị duy nhất trong vị từ, không phải là một tham số có thể thay đổi.

Tomfoolery

Truy vấn "mẹo" của chúng tôi sử dụng vị từ bổ sung cũng không thể sử dụng chỉ mục đã lọc và kết thúc với một kế hoạch phức tạp hơn một chút để khởi động:

SELECT OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0 AND (1 > 0);

TÙY CHỌN (THU HỒI)

Phản ứng điển hình trong trường hợp này, giống như khi xóa cảnh báo trước đó, là thêm OPTION (RECOMPILE) vào tuyên bố. Điều này hoạt động và cho phép chọn chỉ mục đã lọc để tìm kiếm hiệu quả…

SELECT OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0 OPTION (RECOMPILE);

… Nhưng đang thêm OPTION (RECOMPILE) và việc thực hiện biên dịch bổ sung này chống lại mọi thực thi truy vấn không phải lúc nào cũng được chấp nhận trong các môi trường có khối lượng lớn (đặc biệt nếu chúng đã bị ràng buộc bởi CPU).

Gợi ý

Ai đó đã đề xuất gợi ý rõ ràng chỉ mục được lọc để tránh chi phí biên dịch lại. Nói chung, điều này khá giòn, bởi vì nó dựa vào chỉ mục tồn tại lâu hơn của mã; Tôi có xu hướng sử dụng điều này như là một phương sách cuối cùng. Trong trường hợp này, nó không hợp lệ. Khi các quy tắc tham số hóa ngăn trình tối ưu hóa tự động chọn chỉ mục đã lọc, chúng cũng ngăn bạn chọn chỉ mục theo cách thủ công. Vấn đề tương tự với FORCESEEK chung gợi ý:

SELECT OrderID, OrderDate FROM dbo.Orders WITH (INDEX (ix_OrdersNotShipped)) WHERE IsShipped = 0;
 
SELECT OrderID, OrderDate FROM dbo.Orders WITH (FORCESEEK) WHERE IsShipped = 0;

Cả hai đều gây ra lỗi này:

Msg 8622, Mức 16, Trạng thái 1
Bộ xử lý truy vấn không thể tạo kế hoạch truy vấn do các gợi ý được xác định trong truy vấn này. Gửi lại truy vấn mà không chỉ định bất kỳ gợi ý nào và không sử dụng SET FORCEPLAN.

Và điều này có ý nghĩa, bởi vì không có cách nào để biết rằng giá trị không xác định cho IsShipped tham số sẽ khớp với chỉ mục đã lọc (hoặc hỗ trợ thao tác tìm kiếm trên bất kỳ chỉ mục nào).

SQL động?

Tôi đã đề xuất rằng bạn có thể sử dụng SQL động, để ít nhất chỉ trả lần truy cập biên dịch lại đó khi bạn biết mình muốn đạt được chỉ mục nhỏ hơn:

DECLARE @IsShipped bit = 0;
 
DECLARE @sql nvarchar(max) = N'SELECT dynsql = OrderID, OrderDate FROM dbo.Orders'
  + CASE WHEN @IsShipped IS NOT NULL THEN N' WHERE IsShipped = @IsShipped'
    ELSE N'' END
  + CASE WHEN @IsShipped = 0 THEN N' OPTION (RECOMPILE)' ELSE N'' END;
 
EXEC sys.sp_executesql @sql, N'@IsShipped bit', @IsShipped;

Điều này dẫn đến kế hoạch hiệu quả tương tự như trên. Nếu bạn đã thay đổi biến thành @IsShipped = 1 , sau đó bạn sẽ nhận được bản quét chỉ mục theo cụm đắt tiền hơn mà bạn nên mong đợi:

Nhưng không ai thích sử dụng SQL động trong một trường hợp phức tạp như thế này - nó làm cho mã khó đọc và bảo trì hơn, và ngay cả khi mã này đã xuất hiện trong ứng dụng, thì vẫn phải thêm logic bổ sung vào đó, khiến nó ít hơn mong muốn .

Một cái gì đó đơn giản hơn

Chúng tôi đã nói sơ qua về việc triển khai hướng dẫn kế hoạch, điều này chắc chắn không đơn giản hơn, nhưng sau đó một đồng nghiệp đã gợi ý rằng bạn có thể đánh lừa trình tối ưu hóa bằng cách "ẩn" câu lệnh được tham số hóa bên trong một thủ tục, chế độ xem hoặc hàm có giá trị bảng nội tuyến được lưu trữ. Nó quá đơn giản, tôi không tin rằng nó sẽ hoạt động.

Nhưng sau đó tôi đã thử nó:

CREATE PROCEDURE dbo.GetUnshippedOrders
AS
BEGIN
  SET NOCOUNT ON;
  SELECT OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0;
END
GO
 
CREATE VIEW dbo.vUnshippedOrders
AS
  SELECT OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0;
GO
 
CREATE FUNCTION dbo.fnUnshippedOrders()
RETURNS TABLE
AS
  RETURN (SELECT OrderID, OrderDate FROM dbo.Orders WHERE IsShipped = 0);
GO

Cả ba truy vấn này đều thực hiện tìm kiếm hiệu quả đối với chỉ mục được lọc:

EXEC dbo.GetUnshippedOrders;
GO
SELECT OrderID, OrderDate FROM dbo.vUnshippedOrders;
GO
SELECT OrderID, OrderDate FROM dbo.fnUnshippedOrders();

Kết luận

Tôi đã rất ngạc nhiên vì điều này rất hiệu quả. Tất nhiên, điều này đòi hỏi bạn phải thay đổi ứng dụng; nếu bạn không thể thay đổi mã ứng dụng để gọi một quy trình đã lưu trữ hoặc tham chiếu chế độ xem hoặc chức năng (hoặc thậm chí thêm OPTION (RECOMPILE) ), bạn sẽ phải tiếp tục tìm kiếm các tùy chọn khác. Nhưng nếu bạn có thể thay đổi mã ứng dụng, thì việc nhồi vị từ vào một mô-đun khác có thể là một cách để thực hiện.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cách lấy năm từ một ngày trong T-SQL

  2. Cơ sở dữ liệu chỉ sao lưu trong WHM

  3. Thực hiện sao lưu và khôi phục cơ sở dữ liệu tự động với các phương tiện mặc định

  4. Quản lý chỉ mục tự động trong cơ sở dữ liệu Azure SQL

  5. Thay đổi trên Bảng lớn trong RDS Giải pháp cho bảng đầy đủ Lỗi