Là quy tắc WHERE-THAM GIA-ĐẶT HÀNG- (CHỌN) cho thứ tự cột chỉ mục sai?


9

Tôi đang cố gắng cải thiện truy vấn (phụ) này là một phần của truy vấn lớn hơn:

select SUM(isnull(IP.Q, 0)) as Q, 
        IP.OPID 
    from IP
        inner join I
        on I.ID = IP.IID
    where 
        IP.Deleted=0 and
        (I.Status > 0 AND I.Status <= 19) 
    group by IP.OPID

Sentry Plan Explorer đã chỉ ra một số Tra cứu khóa tương đối đắt tiền cho bảng dbo. [I] được thực hiện bởi truy vấn trên.

Bảng dbo.I

    CREATE TABLE [dbo].[I] (
  [ID]  UNIQUEIDENTIFIER NOT NULL,
  [OID]  UNIQUEIDENTIFIER NOT NULL,
  []  UNIQUEIDENTIFIER NOT NULL,
  []  UNIQUEIDENTIFIER NOT NULL,
  [] UNIQUEIDENTIFIER NULL,
  []  UNIQUEIDENTIFIER NOT NULL,
  []  CHAR (3) NOT NULL,
  []  CHAR (3)  DEFAULT ('EUR') NOT NULL,
  []  DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
  [] CHAR (10)  NOT NULL,
  []  DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
  []  DATETIME  DEFAULT (getdate()) NOT NULL,
  []  VARCHAR (35) NULL,
  [] NVARCHAR (100) NOT NULL,
  []  NVARCHAR (100) NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  [Status]  INT DEFAULT ((0)) NOT NULL,
  []  DECIMAL (18, 2)  NOT NULL,
  [] DECIMAL (18, 2)  NOT NULL,
  [] DECIMAL (18, 2)  NOT NULL,
  [] DATETIME DEFAULT (getdate()) NULL,
  []  DATETIME NULL,
  []  NTEXT  NULL,
  []  NTEXT  NULL,
  [] TINYINT  DEFAULT ((0)) NOT NULL,
  []  DATETIME NULL,
  []  VARCHAR (50) NULL,
  []  DATETIME  DEFAULT (getdate()) NOT NULL,
  []  VARCHAR (50) NOT NULL,
  []  DATETIME NULL,
  []  VARCHAR (50) NULL,
  []  ROWVERSION NOT NULL,
  []  DATETIME NULL,
  []  INT  NULL,
  [] TINYINT DEFAULT ((0)) NOT NULL,
  [] UNIQUEIDENTIFIER NULL,
  []  TINYINT DEFAULT ((0)) NOT NULL,
  []  TINYINT  DEFAULT ((0)) NOT NULL,
  [] NVARCHAR (50)  NULL,
  [] TINYINT DEFAULT ((0)) NOT NULL,
  []  UNIQUEIDENTIFIER NULL,
  [] UNIQUEIDENTIFIER NULL,
  []  TINYINT  DEFAULT ((0)) NOT NULL,
  []  TINYINT DEFAULT ((0)) NOT NULL,
  []  UNIQUEIDENTIFIER NULL,
  []  DECIMAL (18, 2)  NULL,
  []  DECIMAL (18, 2)  NULL,
  [] DECIMAL (18, 2)  DEFAULT ((0)) NOT NULL,
  [] UNIQUEIDENTIFIER NULL,
  [] DATETIME NULL,
  [] DATETIME NULL,
  []  VARCHAR (35) NULL,
  [] DECIMAL (18, 2)  DEFAULT ((0)) NOT NULL,
  CONSTRAINT [PK_I] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
  CONSTRAINT [FK_I_O] FOREIGN KEY ([OID]) REFERENCES [dbo].[O] ([ID]),
  CONSTRAINT [FK_I_Status] FOREIGN KEY ([Status]) REFERENCES [dbo].[T_Status] ([Status])
);                  


GO
CREATE CLUSTERED INDEX [CIX_Invoice]
  ON [dbo].[I]([OID] ASC) WITH (FILLFACTOR = 90);

Bảng dbo.IP

CREATE TABLE [dbo].[IP] (
 [ID] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
 [IID] UNIQUEIDENTIFIER NOT NULL,
 [OID] UNIQUEIDENTIFIER NOT NULL,
 [Deleted] TINYINT DEFAULT ((0)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 []UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] INT NOT NULL,
 [] VARCHAR (35) NULL,
 [] NVARCHAR (100) NOT NULL,
 [] NTEXT NULL,
 [] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
 [] NTEXT NULL,
 [] NTEXT NULL,
 [] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
 [] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
 [] DECIMAL (4, 2) NOT NULL,
 [] INT DEFAULT ((1)) NOT NULL,
 [] DATETIME DEFAULT (getdate()) NOT NULL,
 [] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [] DATETIME NULL,
 [] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
 [] ROWVERSION NOT NULL,
 [] INT DEFAULT ((1)) NOT NULL,
 [] DATETIME NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
 [] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
 [] INT DEFAULT ((0)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 []UNIQUEIDENTIFIER NULL,
 []NVARCHAR (35) NULL,
 [] VARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] NVARCHAR (35) NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] VARCHAR (12) NULL,
 [] VARCHAR (4) NULL,
 [] NVARCHAR (50) NULL,
 [] NVARCHAR (50) NULL,
 [] VARCHAR (35) NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] NVARCHAR (50) NULL,
 [] TINYINT DEFAULT ((0)) NOT NULL,
 [] DECIMAL (18, 2) NULL,
 []TINYINT DEFAULT ((1)) NOT NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] UNIQUEIDENTIFIER NULL,
 [] TINYINT DEFAULT ((1)) NOT NULL,
 CONSTRAINT [PK_IP] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
 CONSTRAINT [FK_IP_I] FOREIGN KEY ([IID]) REFERENCES [dbo].[I] ([ID]) ON DELETE CASCADE NOT FOR REPLICATION,
 CONSTRAINT [FK_IP_XType] FOREIGN KEY ([XType]) REFERENCES [dbo].[xTYPE] ([Value]) NOT FOR REPLICATION
);

GO
CREATE CLUSTERED INDEX [IX_IP_CLUST]
 ON [dbo].[IP]([IID] ASC) WITH (FILLFACTOR = 90);

Bảng "I" có khoảng 100.000 hàng, chỉ mục được nhóm có 9.386 trang.
IP bảng là "con" - bảng của I và có khoảng 175.000 hàng.

Tôi đã cố gắng thêm một chỉ mục mới theo quy tắc thứ tự cột chỉ mục: "WHERE-THAM GIA-ĐẶT HÀNG- (CHỌN)"

https://www.mssqltips.com/sqlservertutorial/3208/use-where-join-orderby-select-column-order-when-creating-indexes/

để giải quyết các tra cứu chính và tạo chỉ mục tìm kiếm:

CREATE NONCLUSTERED INDEX [IX_I_Status_1]
    ON [dbo].[Invoice]([Status], [ID])

Các truy vấn trích xuất ngay lập tức sử dụng chỉ mục này. Nhưng truy vấn lớn hơn ban đầu nó là một phần của, không. Nó thậm chí không sử dụng nó khi tôi buộc nó sử dụng VỚI (INDEX (IX_I_Status_1)).

Sau một thời gian, tôi quyết định thử một chỉ mục mới khác và thay đổi thành thứ tự của các cột được lập chỉ mục:

CREATE NONCLUSTERED INDEX [IX_I_Status_2]
    ON [dbo].[Invoice]([ID], [Status])

CHÀO! Chỉ mục này được sử dụng bởi truy vấn trích xuất và truy vấn lớn hơn!

Sau đó, tôi đã so sánh các thống kê IO được trích xuất bằng cách buộc nó sử dụng [IX_I_Status_1] và [IX_I_Status_2]:

Kết quả [IX_I_Status_1]:

Table 'I'. Scan count 5, logical reads 636, physical reads 16, read-ahead reads 574
Table 'IP'. Scan count 5, logical reads 1134, physical reads 11, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0

Kết quả [IX_I_Status_2]:

Table 'I'. Scan count 1, logical reads 615, physical reads 6, read-ahead reads 631
Table 'IP'. Scan count 1, logical reads 1024, physical reads 5, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0,  read-ahead reads 0

OK, tôi có thể hiểu rằng truy vấn quái vật khổng lồ có thể quá phức tạp để khiến máy chủ SQL bắt được kế hoạch thực hiện lý tưởng và có thể bỏ lỡ chỉ mục mới của tôi. Nhưng tôi không hiểu tại sao chỉ mục [IX_I_Status_2] dường như phù hợp hơn và hiệu quả hơn cho truy vấn.

Vì truy vấn đầu tiên của tất cả các bộ lọc bảng I theo cột STATUS và sau đó kết hợp với IP bảng, tôi không hiểu tại sao [IX_I_Status_2] tốt hơn và được sử dụng bởi Sql Server thay vì [IX_I_Status_1]?


Có, nó sử dụng chỉ số này trong trường hợp các tiêu chí lọc đáp ứng. Nó thực hiện quét chỉ mục (giống như với IX_I_Status_2) và so với điều này, nó tiết kiệm được 1 lần đọc vật lý. nhưng tôi đã phải "bao gồm (trạng thái)" cho chỉ mục này vì trạng thái nằm ở đầu ra và một lần nữa được tra cứu trước đó.
Magier

Lưu ý bên lề: Sau khi tôi áp dụng cho chỉ mục tốt nhất, tôi có thể tìm ra ([IX_I_Status_2]) và chạy lại truy vấn, bây giờ tôi nhận được một đề xuất chỉ mục bị thiếu: TẠO INDEX KHÔNG TẠO [ dbo]. [I] ([Trạng thái]) INCLUDE ([ID]) Đây là một đề xuất kém và làm giảm hiệu suất của truy vấn. Máy chủ TY Sql :)
Magier

Câu trả lời:


19

Là quy tắc WHERE-THAM GIA-ĐẶT HÀNG- (CHỌN) cho thứ tự cột chỉ mục sai?

Ít nhất đó là lời khuyên không đầy đủ và có khả năng gây hiểu lầm (tôi không buồn đọc toàn bộ bài viết). Nếu bạn định đọc nội dung trên Internet (bao gồm cả điều này), bạn nên điều chỉnh mức độ tin cậy của mình theo mức độ bạn đã biết và tin tưởng tác giả, nhưng sau đó luôn tự xác minh.

Có một số "quy tắc ngón tay cái" để tạo chỉ mục, tùy thuộc vào kịch bản chính xác, nhưng không có cái nào thực sự thay thế tốt để hiểu các vấn đề cốt lõi cho chính bạn. Đọc về việc thực hiện các chỉ mục và các toán tử kế hoạch thực hiện trong SQL Server, xem qua một số bài tập và hiểu rõ về cách các chỉ mục có thể được sử dụng để làm cho các kế hoạch thực hiện hiệu quả hơn. Không có lối tắt hiệu quả để đạt được kiến ​​thức và kinh nghiệm này.

Nói chung, tôi có thể nói rằng các chỉ mục của bạn thường nên có các cột được sử dụng cho các kiểm tra đẳng thức trước tiên, với bất kỳ bất đẳng thức nào cuối cùng và / hoặc được cung cấp bởi một bộ lọc trên chỉ mục. Đây không phải là một tuyên bố hoàn chỉnh, bởi vì các chỉ mục cũng có thể cung cấp thứ tự, có thể hữu ích hơn là tìm kiếm trực tiếp đến một hoặc nhiều khóa trong một số tình huống. Ví dụ: đặt hàng có thể được sử dụng để tránh sắp xếp, để giảm chi phí của tùy chọn tham gia vật lý như hợp nhất kết hợp, để cho phép tổng hợp luồng, nhanh chóng tìm thấy một vài hàng đủ điều kiện đầu tiên ... v.v.

Tôi hơi mơ hồ ở đây, vì việc chọn (các) chỉ số lý tưởng cho một truy vấn phụ thuộc vào rất nhiều yếu tố - đây là một chủ đề rất rộng.

Dù sao, không có gì lạ khi tìm thấy các tín hiệu mâu thuẫn cho các chỉ mục 'tốt nhất' trong một truy vấn. Ví dụ: vị từ tham gia của bạn muốn các hàng được sắp xếp một cách cho liên kết hợp nhất, nhóm theo muốn các hàng được sắp xếp theo cách khác cho tổng hợp luồng và tìm các hàng đủ điều kiện bằng cách sử dụng các vị từ mệnh đề sẽ gợi ý các chỉ mục khác.

Lý do lập chỉ mục là một nghệ thuật cũng như khoa học là một sự kết hợp lý tưởng không phải lúc nào cũng có thể hợp lý. Chọn các chỉ mục thỏa hiệp tốt nhất cho khối lượng công việc (không chỉ là một truy vấn duy nhất) đòi hỏi các kỹ năng phân tích, kinh nghiệm và kiến ​​thức cụ thể về hệ thống. Nếu nó dễ dàng , các công cụ tự động sẽ hoàn hảo và các chuyên gia tư vấn điều chỉnh hiệu suất sẽ có nhu cầu ít hơn nhiều.

Theo như đề xuất chỉ số còn thiếu có liên quan: đây là những cơ hội. Trình tối ưu hóa khiến họ chú ý khi nó cố gắng khớp các vị từ và thứ tự sắp xếp cần thiết cho một chỉ mục không tồn tại. Do đó, các đề xuất dựa trên các nỗ lực đối sánh cụ thể trong bối cảnh cụ thể của biến thể kế hoạch phụ cụ thể mà nó đang xem xét tại thời điểm đó.

Trong bối cảnh, các đề xuất luôn có ý nghĩa, về mặt giảm chi phí ước tính của việc truy cập dữ liệu, theo mô hình của trình tối ưu hóa. Nó không thực hiện phân tích rộng hơn về toàn bộ truy vấn (ít hơn khối lượng công việc rộng hơn), vì vậy bạn nên nghĩ về các đề xuất này như một gợi ý nhẹ nhàng mà một người có kỹ năng cần xem xét các chỉ mục có sẵn, với các đề xuất là bắt đầu điểm (và thường không nhiều hơn thế).

Trong trường hợp của bạn, (Status) INCLUDE (ID)đề xuất có thể xuất hiện khi xem xét khả năng băm hoặc hợp nhất tham gia (ví dụ sau). Trong bối cảnh hẹp đó, đề nghị có ý nghĩa. Đối với các truy vấn như một toàn thể, có thể không. Chỉ mục (ID, Status)cho phép tham gia vòng lặp lồng nhau IDnhư một tham chiếu bên ngoài: tìm kiếm sự bình đẳng IDvà bất bình đẳng trên Statusmỗi lần lặp.

Một lựa chọn có thể của các chỉ mục là:

CREATE INDEX i1 ON dbo.I (ID, [Status]);
CREATE INDEX i1 ON dbo.IP (Deleted, OPID, IID) INCLUDE (Q);

... tạo ra một kế hoạch như:

Kế hoạch có thể

Tôi không nói rằng các chỉ số này là tối ưu cho bạn; họ tình cờ làm việc để tạo ra một kế hoạch hợp lý cho tôi, mà không thể xem số liệu thống kê cho các bảng liên quan, hoặc các định nghĩa đầy đủ và lập chỉ mục hiện có. Ngoài ra, tôi không biết gì về khối lượng công việc rộng hơn hoặc truy vấn thực sự.

Ngoài ra (chỉ để hiển thị một trong vô số khả năng bổ sung):

CREATE INDEX i1 ON dbo.I ([Status]) INCLUDE (ID);
CREATE INDEX i1 ON dbo.IP (Deleted, IID, OPID) INCLUDE (Q);

Cung cấp:

Kế hoạch thay thế

Các kế hoạch thực hiện được tạo bằng SQL Sentry Plan Explorer .

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.