Tại sao chỉ mục của tôi không được sử dụng trong CHỌN HÀNG ĐẦU?


15

Đây là sự cố: Tôi đang thực hiện một truy vấn chọn. Mỗi cột trong mệnh đề WHEREORDER BYtrong một chỉ mục không được nhóm IX_MachineryId_DateRecorded, là một phần của khóa hoặc dưới dạng INCLUDEcột. Tôi đang chọn tất cả các cột, do đó sẽ dẫn đến việc tra cứu dấu trang, nhưng tôi chỉ lấy TOP (1), vì vậy chắc chắn máy chủ có thể cho biết việc tra cứu chỉ cần được thực hiện một lần, vào cuối.

Quan trọng nhất, khi tôi buộc truy vấn sử dụng chỉ mục IX_MachineryId_DateRecorded, nó sẽ chạy trong chưa đầy một giây. Nếu tôi để máy chủ quyết định sử dụng chỉ mục nào, nó sẽ chọn IX_MachineryIdvà mất tới một phút. Điều đó thực sự gợi ý cho tôi rằng tôi đã thực hiện đúng chỉ mục và máy chủ đang đưa ra một quyết định tồi tệ. Tại sao?

CREATE TABLE [dbo].[MachineryReading] (
    [Id]                 INT              IDENTITY (1, 1) NOT NULL,
    [Location]           [sys].[geometry] NULL,
    [Latitude]           FLOAT (53)       NOT NULL,
    [Longitude]          FLOAT (53)       NOT NULL,
    [Altitude]           FLOAT (53)       NULL,
    [Odometer]           INT              NULL,
    [Speed]              FLOAT (53)       NULL,
    [BatteryLevel]       INT              NULL,
    [PinFlags]           BIGINT           NOT NULL,
    [DateRecorded]       DATETIME         NOT NULL,
    [DateReceived]       DATETIME         NOT NULL,
    [Satellites]         INT              NOT NULL,
    [HDOP]               FLOAT (53)       NOT NULL,
    [MachineryId]        INT              NOT NULL,
    [TrackerId]          INT              NOT NULL,
    [ReportType]         NVARCHAR (1)     NULL,
    [FixStatus]          INT              DEFAULT ((0)) NOT NULL,
    [AlarmStatus]        INT              DEFAULT ((0)) NOT NULL,
    [OperationalSeconds] INT              DEFAULT ((0)) NOT NULL,
    CONSTRAINT [PK_dbo.MachineryReading] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_dbo.MachineryReading_dbo.Machinery_MachineryId] FOREIGN KEY ([MachineryId]) REFERENCES [dbo].[Machinery] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_dbo.MachineryReading_dbo.Tracker_TrackerId] FOREIGN KEY ([TrackerId]) REFERENCES [dbo].[Tracker] ([Id]) ON DELETE CASCADE
);

GO
CREATE NONCLUSTERED INDEX [IX_MachineryId]
    ON [dbo].[MachineryReading]([MachineryId] ASC);

GO
CREATE NONCLUSTERED INDEX [IX_TrackerId]
    ON [dbo].[MachineryReading]([TrackerId] ASC);

GO
CREATE NONCLUSTERED INDEX [IX_MachineryId_DateRecorded]
    ON [dbo].[MachineryReading]([MachineryId] ASC, [DateRecorded] ASC)
    INCLUDE([OperationalSeconds], [FixStatus]);

Bảng được phân chia thành các phạm vi tháng (mặc dù tôi vẫn không thực sự hiểu những gì đang diễn ra ở đó).

ALTER PARTITION SCHEME PartitionSchemeMonthRange NEXT USED [Primary]
ALTER PARTITION FUNCTION [PartitionFunctionMonthRange]() SPLIT RANGE(N'2016-01-01T00:00:00.000') 

ALTER PARTITION SCHEME PartitionSchemeMonthRange NEXT USED [Primary]
ALTER PARTITION FUNCTION [PartitionFunctionMonthRange]() SPLIT RANGE(N'2016-02-01T00:00:00.000') 
...

CREATE UNIQUE CLUSTERED INDEX [PK_dbo.MachineryReadingPs] ON MachineryReading(DateRecorded, Id) ON PartitionSchemeMonthRange(DateRecorded)

Truy vấn mà tôi thường chạy:

SELECT TOP (1) [Id], [Location], [Latitude], [Longitude], [Altitude], [Odometer], [ReportType], [FixStatus], [AlarmStatus], [Speed], [BatteryLevel], [PinFlags], [DateRecorded], [DateReceived], [Satellites], [HDOP], [OperationalSeconds], [MachineryId], [TrackerId]
    FROM [dbo].[MachineryReading]
    --WITH(INDEX(IX_MachineryId_DateRecorded)) --This makes all the difference
    WHERE ([MachineryId] = @p__linq__0) AND ([DateRecorded] >= @p__linq__1) AND ([DateRecorded] < @p__linq__2) AND ([OperationalSeconds] > 0)
    ORDER BY [DateRecorded] ASC

Gói truy vấn: https://www.brentozar.com/pastetheplan/?id=r1c-RpxNx

Gói truy vấn với chỉ mục bắt buộc: https://www.brentozar.com/pastetheplan/?id=SywwTagVe

Các kế hoạch bao gồm là các kế hoạch thực hiện thực tế, nhưng trên cơ sở dữ liệu dàn (khoảng 1/100 kích thước của cuộc sống). Tôi do dự khi loay hoay với cơ sở dữ liệu trực tiếp vì tôi chỉ mới bắt đầu ở công ty này khoảng một tháng trước.

Tôi có cảm giác đó là do phân vùng và truy vấn của tôi thường kéo dài mọi phân vùng duy nhất (ví dụ: khi tôi muốn nhận bản ghi đầu tiên hoặc cuối cùng OperationalSecondsđược ghi cho một máy). Tuy nhiên, tất cả các truy vấn tôi đã viết bằng tay đều chạy nhanh hơn 10 - 100 lần so với những gì EntityFramework đã tạo, vì vậy tôi sẽ thực hiện một quy trình được lưu trữ.


1
Xin chào @AndrewWilliamson, Đây có thể là sự cố về số liệu thống kê. Nếu bạn thấy kế hoạch thực tế từ kế hoạch chưa thực hiện, số lượng hàng ước tính là 1,22 và thực tế là 19039. Điều này lần lượt dẫn đến việc tra cứu chính mà bạn sẽ thấy sau đó trong kế hoạch. Bạn đã cố gắng để cập nhật số liệu thống kê? Nếu không, hãy thử quét toàn bộ cơ sở dữ liệu dàn.
jesijesi

Câu trả lời:


21

Nếu tôi để máy chủ quyết định sử dụng chỉ mục nào, nó sẽ chọn IX_MachineryIdvà mất tới một phút.

Chỉ mục đó không được phân vùng, vì vậy trình tối ưu hóa nhận ra nó có thể được sử dụng để cung cấp thứ tự được chỉ định trong truy vấn mà không cần sắp xếp. Là một chỉ mục không độc nhất, nó cũng có các khóa của chỉ mục được nhóm là các khóa con, vì vậy chỉ mục có thể được sử dụng để tìm kiếm MachineryIdDateRecordedphạm vi:

Tìm kiếm chỉ mục

Chỉ mục không bao gồm OperationalSeconds, vì vậy kế hoạch phải tìm giá trị đó trên mỗi hàng trong chỉ mục được phân cụm (được phân vùng) để kiểm tra OperationalSeconds > 0:

Tra cứu

Trình tối ưu hóa ước tính rằng một hàng sẽ cần phải được đọc từ chỉ mục không bao gồm và tra cứu để đáp ứng TOP (1). Tính toán này dựa trên mục tiêu hàng (nhanh chóng tìm một hàng) và giả định phân phối giá trị thống nhất.

Từ kế hoạch thực tế, chúng ta có thể thấy ước tính 1 hàng là không chính xác. Trong thực tế, 19.039 hàng phải được xử lý để phát hiện ra rằng không có hàng nào thỏa mãn các điều kiện truy vấn. Đây là trường hợp xấu nhất để tối ưu hóa mục tiêu hàng (ước tính 1 hàng, tất cả các hàng thực sự cần thiết):

Thực tế / ước tính

Bạn có thể vô hiệu hóa các mục tiêu hàng với cờ theo dõi 4138 . Điều này rất có thể sẽ dẫn đến việc SQL Server chọn một gói khác, có thể là gói bạn đã buộc. Trong mọi trường hợp, chỉ mục IX_MachineryIdcó thể được thực hiện tối ưu hơn bằng cách bao gồm OperationalSeconds.

Điều khá bất thường là có các chỉ mục không bao gồm không liên kết (các chỉ mục được phân vùng theo một cách khác với bảng cơ sở, bao gồm cả không).

Điều đó thực sự gợi ý cho tôi rằng tôi đã thực hiện đúng chỉ mục và máy chủ chỉ đưa ra một quyết định tồi tệ. Tại sao?

Như thường lệ, trình tối ưu hóa đang chọn gói rẻ nhất mà nó xem xét.

Chi phí ước tính của IX_MachineryIdkế hoạch là 0,01 đơn vị chi phí, dựa trên giả định mục tiêu hàng (không chính xác) rằng một hàng sẽ được kiểm tra và trả lại.

Chi phí ước tính của IX_MachineryId_DateRecordedkế hoạch cao hơn nhiều, ở mức 0,27 đơn vị, chủ yếu là vì họ dự kiến ​​sẽ đọc 5.515 hàng từ chỉ mục, sắp xếp chúng và trả về giá trị thấp nhất (theo DateRecorded):

Sắp xếp N hàng đầu

Chỉ mục này được phân vùng và không thể trả về các hàng theo DateRecordedthứ tự trực tiếp (xem phần sau). Nó có thể tìm kiếm MachineryIdDateRecordedphạm vi trong mỗi phân vùng , nhưng cần phải có Sắp xếp:

Tìm kiếm phân vùng

Nếu chỉ mục này không được phân vùng, một loại sẽ không được yêu cầu và nó sẽ rất giống với chỉ mục (không liên kết) khác với cột được thêm vào. Một chỉ mục được lọc không liên kết sẽ vẫn hiệu quả hơn một chút.


Bạn nên cập nhật truy vấn nguồn sao cho kiểu dữ liệu của @From@Totham số khớp với DateRecordedcột ( datetime). Hiện tại, SQL Server đang tính toán một phạm vi động do loại không khớp trong thời gian chạy (sử dụng toán tử Merge Interval và cây con của nó):

<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@From],NULL,(22))">
<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@To],NULL,(22))">

Chuyển đổi này ngăn trình tối ưu hóa suy luận chính xác về mối quan hệ giữa ID phân vùng tăng dần (bao gồm một phạm vi các DateRecordedgiá trị theo thứ tự tăng dần) và các biến vị ngữ bất bình đẳng trên DateRecorded.

ID phân vùng là một khóa hàng đầu ngầm định cho một chỉ mục được phân vùng. Thông thường, trình tối ưu hóa có thể thấy rằng việc sắp xếp theo ID phân vùng (trong đó ID tăng dần ánh xạ tới tăng dần, tách rời các giá trị của DateRecorded) sau đó DateRecordedgiống như sắp xếp theo thứ tự DateRecorded(cho rằng MachineryIDkhông đổi). Chuỗi lý luận này bị phá vỡ bởi chuyển đổi loại.

Bản giới thiệu

Một bảng và chỉ mục được phân vùng đơn giản:

CREATE PARTITION FUNCTION PF (datetime)
AS RANGE LEFT FOR VALUES ('20160101', '20160201', '20160301');

CREATE PARTITION SCHEME PS AS PARTITION PF ALL TO ([PRIMARY]);

CREATE TABLE dbo.T (c1 integer NOT NULL, c2 datetime NOT NULL) ON PS (c2);

CREATE INDEX i ON dbo.T (c1, c2) ON PS (c2);

INSERT dbo.T (c1, c2) 
VALUES (1, '20160101'), (1, '20160201'), (1, '20160301');

Truy vấn với các loại khớp

-- Types match (datetime)
DECLARE 
    @From datetime = '20010101',
    @To datetime = '20090101';

-- Seek with no sort
SELECT T2.c2 
FROM dbo.T AS T2 
WHERE T2.c1 = 1 
AND T2.c2 >= @From
AND T2.c2 < @To
ORDER BY 
    T2.c2;

Tìm kiếm không có loại

Truy vấn với các loại không khớp

-- Mismatched types (datetime2 vs datetime)
DECLARE 
    @From datetime2 = '20010101',
    @To datetime2 = '20090101';

-- Merge Interval and Sort
SELECT T2.c2 
FROM dbo.T AS T2 
WHERE T2.c1 = 1 
AND T2.c2 >= @From
AND T2.c2 < @To
ORDER BY 
    T2.c2;

Hợp nhất khoảng và sắp xếp


5

Chỉ mục có vẻ khá tốt cho truy vấn và tôi không chắc tại sao nó không được chọn bởi trình tối ưu hóa (thống kê? Phân vùng? Giới hạn phương vị?, Thực sự không có ý tưởng nào.)

Nhưng một chỉ mục được lọc sẽ còn tốt hơn cho truy vấn cụ thể, nếu đó > 0là một giá trị cố định và không thay đổi từ một thực thi truy vấn này sang một truy vấn khác:

CREATE NONCLUSTERED INDEX IX_MachineryId_DateRecorded_filtered
    ON dbo.MachineryReading
        (MachineryId, DateRecorded) 
    WHERE (OperationalSeconds > 0) ;

Có hai sự khác biệt giữa chỉ mục bạn có trong đó OperationalSecondslà cột thứ 3 và chỉ mục được lọc:

  • Đầu tiên, chỉ mục được lọc nhỏ hơn, cả về chiều rộng (hẹp hơn) và số lượng hàng.
    Điều này làm cho chỉ mục được lọc nói chung hiệu quả hơn vì SQL Server cần ít không gian hơn để giữ nó trong bộ nhớ.

  • Thứ hai và điều này tinh tế và quan trọng hơn cho truy vấn là nó chỉ có các hàng khớp với bộ lọc được sử dụng trong truy vấn. Điều này có thể cực kỳ quan trọng, tùy thuộc vào các giá trị của cột thứ 3 này.
    Ví dụ: một bộ tham số cụ thể cho MachineryIdDateRecordedcó thể mang lại 1000 hàng. Nếu tất cả hoặc gần như tất cả các hàng này khớp với (OperationalSeconds > 0)bộ lọc, cả hai chỉ mục sẽ hoạt động tốt. Nhưng nếu các hàng khớp với bộ lọc là rất ít (hoặc chỉ là hàng cuối cùng hoặc không có gì cả), chỉ mục đầu tiên sẽ phải trải qua rất nhiều hoặc tất cả 1000 hàng đó cho đến khi tìm thấy kết quả khớp. Mặt khác, chỉ mục được lọc chỉ cần một tìm kiếm để tìm một hàng khớp (hoặc trả về 0 hàng) vì chỉ các hàng khớp với bộ lọc được lưu trữ.


1
Có thêm chỉ mục làm cho truy vấn hiệu quả hơn?
ypercubeᵀᴹ

Không phải cơ sở dữ liệu dàn dựng (nó thực sự cần nhiều dữ liệu trong đó để kiểm tra chính xác), tôi chưa thử nó trên mạng, các chỉ mục mới mất hơn một giờ để xây dựng trên đó. Tôi cũng khá do dự khi làm bất cứ điều gì với cơ sở dữ liệu trực tiếp của chúng tôi, vì nó đã chạy chậm. Chúng tôi cần một hệ thống tốt hơn để nhân bản cuộc sống của chúng tôi thành dàn dựng.
Andrew Williamson
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.