ทำไมดัชนีของฉันไม่ถูกใช้ใน SELECT TOP?


15

นี่คือการทำงานที่ลดลง: ฉันกำลังทำแบบสอบถามเลือก ทุกคอลัมน์ในWHEREและส่วนORDER BYคำสั่งจะอยู่ในดัชนีที่ไม่ใช่คลัสเตอร์IX_MachineryId_DateRecordedเดียวซึ่งเป็นส่วนหนึ่งของคีย์หรือเป็นINCLUDEคอลัมน์ ฉันกำลังเลือกคอลัมน์ทั้งหมดเพื่อที่จะส่งผลให้มีการค้นหาบุ๊กมาร์ก แต่ฉันกำลังทำอยู่TOP (1)ดังนั้นเซิร์ฟเวอร์จึงสามารถบอกได้ว่าการค้นหาจำเป็นต้องทำเพียงครั้งเดียวในตอนท้าย

สิ่งสำคัญที่สุดคือเมื่อฉันบังคับให้แบบสอบถามใช้ดัชนีIX_MachineryId_DateRecordedมันจะทำงานในเวลาน้อยกว่าหนึ่งวินาที ถ้าฉันปล่อยให้เซิร์ฟเวอร์ตัดสินใจว่าจะใช้ดัชนีใดมันจะเลือกIX_MachineryIdและใช้เวลาประมาณหนึ่งนาที ที่แนะนำให้ฉันจริง ๆ ว่าฉันได้ทำดัชนีถูกต้องและเซิร์ฟเวอร์เพิ่งตัดสินใจไม่ถูกต้อง ทำไม?

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]);

ตารางถูกแบ่งเป็นช่วงเดือน (แต่ฉันยังไม่เข้าใจว่าเกิดอะไรขึ้นที่นั่น)

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)

แบบสอบถามที่ฉันจะเรียกใช้ตามปกติ:

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

แผนคำถาม: https://www.brentozar.com/pastetheplan/?id=r1c-RpxNx

แผนการสืบค้นพร้อมดัชนีบังคับ: https://www.brentozar.com/pastetheplan/?id=SywwTagVe

แผนรวมเป็นแผนปฏิบัติการจริง แต่ในฐานข้อมูลการจัดเตรียม (ประมาณ 1/100 ของขนาดสด) ฉันลังเลที่จะเล่นซอกับฐานข้อมูลสดเพราะฉันเพิ่งเริ่มต้นที่ บริษัท นี้ประมาณหนึ่งเดือนที่ผ่านมา

ฉันรู้สึกว่ามันเกิดจากการแบ่งพาร์ติชันและโดยทั่วไปคิวรีของฉันจะครอบคลุมทุกพาร์ติชันเดียว (เช่นเมื่อฉันต้องการบันทึกครั้งแรกหรือครั้งสุดท้ายที่OperationalSecondsเคยบันทึกไว้สำหรับหนึ่งเครื่อง) อย่างไรก็ตามข้อความค้นหาที่ฉันเขียนด้วยมือทั้งหมดนั้นทำงานได้เร็วกว่าสิ่งที่EntityFrameworkสร้าง10 - 100 เท่าดังนั้นฉันเพิ่งจะทำขั้นตอนการจัดเก็บ


1
สวัสดี @AndrewWilliamson มันอาจเป็นปัญหาสถิติ หากคุณเห็นแผนจริงจากแผนที่ไม่ได้ใช้งานจำนวนแถวโดยประมาณคือ 1.22 และตามจริงคือ 19039 ซึ่งจะนำไปสู่การค้นหาคีย์ซึ่งคุณจะเห็นในภายหลังในแผน คุณพยายามอัปเดตสถิติหรือไม่ หากไม่ลองใช้การสแกนแบบเต็มบนฐานข้อมูลการจัดเตรียม
jesijesi

คำตอบ:


21

ถ้าฉันปล่อยให้เซิร์ฟเวอร์ตัดสินใจว่าจะใช้ดัชนีใดมันจะเลือกIX_MachineryIdและใช้เวลาประมาณหนึ่งนาที

ดัชนีนั้นไม่ได้ถูกแบ่งพาร์ติชันดังนั้นเครื่องมือเพิ่มประสิทธิภาพจะรับรู้ว่าสามารถใช้เพื่อจัดเตรียมการสั่งซื้อที่ระบุในแบบสอบถามโดยไม่ต้องเรียงลำดับ ในฐานะที่เป็นดัชนี nonclustered ที่ไม่ซ้ำกันมันยังมีกุญแจของดัชนีคลัสเตอร์เป็นคีย์ย่อยดังนั้นดัชนีสามารถใช้ในการค้นหาMachineryIdและDateRecordedช่วง:

ดัชนีค้นหา

ดัชนีไม่รวมOperationalSecondsดังนั้นแผนจะต้องค้นหาค่านั้นต่อแถวในดัชนีคลัสเตอร์ (พาร์ติชัน) เพื่อทดสอบOperationalSeconds > 0:

ค้นหา

เครื่องมือเพิ่มประสิทธิภาพจะประมาณว่าจะต้องอ่านแถวหนึ่งจากดัชนีที่ไม่เป็นคลัสเตอร์และค้นหาเพื่อสนองความต้องการ TOP (1)และเงยหน้าขึ้นเพื่อตอบสนองความ การคำนวณนี้ขึ้นอยู่กับเป้าหมายของแถว (ค้นหาหนึ่งแถวอย่างรวดเร็ว) และถือว่าการกระจายของค่าสม่ำเสมอ

จากแผนจริงเราสามารถเห็นค่าประมาณ 1 แถวไม่ถูกต้อง ในความเป็นจริงต้องประมวลผลแถว 19,039 เพื่อค้นพบว่าไม่มีแถวใดตรงตามเงื่อนไขการสืบค้น นี่เป็นกรณีที่แย่ที่สุดสำหรับการเพิ่มประสิทธิภาพแถวเป้าหมาย (ประมาณ 1 แถวต้องการแถวทั้งหมดจริง):

จริง / ประมาณการ

คุณสามารถปิดเป้าหมายแถวที่มีร่องรอยธง 4138 นี่น่าจะส่งผลให้ SQL Server เลือกแผนอื่นอาจเป็นแผนที่คุณบังคับ ในกรณีใด ๆ ดัชนีอาจจะทำดีที่สุดมากขึ้นโดยรวมIX_MachineryIdOperationalSeconds

มันค่อนข้างผิดปกติที่จะมีดัชนีที่ไม่จัดกลุ่มแบบไม่จัดกลุ่ม (ดัชนีถูกแบ่งพาร์ติชันด้วยวิธีที่แตกต่างจากตารางฐานรวมถึงไม่ได้เลย)

ที่แนะนำให้ฉันจริง ๆ ว่าฉันได้ทำดัชนีถูกต้องและเซิร์ฟเวอร์เพิ่งตัดสินใจไม่ถูกต้อง ทำไม?

ตามปกติเครื่องมือเพิ่มประสิทธิภาพกำลังเลือกแผนการที่ถูกที่สุดที่จะพิจารณา

ค่าใช้จ่ายโดยประมาณของIX_MachineryIdแผนคือ 0.01 หน่วยต้นทุนขึ้นอยู่กับสมมติฐานของแถวเป้าหมาย (ไม่ถูกต้อง) ที่จะทำการทดสอบและส่งคืนหนึ่งแถว

ค่าใช้จ่ายโดยประมาณของIX_MachineryId_DateRecordedแผนสูงกว่ามากที่ 0.27 หน่วยส่วนใหญ่เป็นเพราะคาดว่าจะอ่าน 5,515 แถวจากดัชนีเรียงลำดับและส่งคืนค่าที่เรียงลำดับต่ำสุด (โดยDateRecorded):

N อันดับสูงสุด

ดัชนีนี้ถูกแบ่งพาร์ติชันและไม่สามารถส่งคืนแถวDateRecordedตามลำดับได้โดยตรง (ดูภายหลัง) มันสามารถค้นหาMachineryIdและDateRecordedช่วงภายในแต่ละพาร์ติชันแต่จำเป็นต้องมีการเรียงลำดับ:

แยกพาร์ติชัน

หากดัชนีนี้ไม่ได้รับการแบ่งพาร์ติชันจะไม่จำเป็นต้องมีการเรียงลำดับและจะคล้ายกับดัชนีอื่น ๆ (ไม่แบ่งพาร์ติชั่น) พร้อมคอลัมน์พิเศษเพิ่มเติม ดัชนีที่กรองแบบไม่แบ่งชั้นจะยังคงมีประสิทธิภาพมากขึ้นเล็กน้อย


คุณควรปรับปรุงคิวรีแหล่งข้อมูลเพื่อให้ชนิดข้อมูลของ@Fromและ@Toพารามิเตอร์ตรงกับDateRecordedคอลัมน์ ( datetime) ในขณะนี้ SQL Server กำลังคำนวณช่วงไดนามิกเนื่องจากชนิดไม่ตรงกันในขณะใช้งาน (โดยใช้ตัวดำเนินการ Merge Interval และแผนผังย่อย):

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

การแปลงนี้ป้องกันเครื่องมือเพิ่มประสิทธิภาพจากการให้เหตุผลอย่างถูกต้องเกี่ยวกับความสัมพันธ์ระหว่างIDพาร์ติชันจากน้อยไปหามาก(ครอบคลุมช่วงของDateRecordedค่าในลำดับจากน้อยไปหามาก) และความไม่เท่าเทียมกันบนDateRecordedของเพรดิเคต

ID พาร์ติชันเป็นคีย์นำโดยนัยสำหรับดัชนีที่แบ่งพาร์ติชัน โดยทั่วไปเครื่องมือเพิ่มประสิทธิภาพจะเห็นว่าการสั่งซื้อโดย ID พาร์ทิชัน (ที่แผนที่ ID น้อยไปหามากไปน้อยและแยกค่าDateRecorded) จากนั้นDateRecordedจะเหมือนกับการสั่งซื้อโดยDateRecordedลำพัง (ให้ที่MachineryIDคงที่) สายโซ่แห่งการให้เหตุผลนี้ถูกทำลายโดยการแปลงรูปแบบ

การสาธิต

ตารางและดัชนีที่แบ่งพาร์ติชันอย่างง่าย:

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');

ค้นหาด้วยประเภทที่ตรงกัน

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

แสวงหาการจัดเรียง

คำค้นหาที่มีประเภทไม่ตรงกัน

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

ผสานช่วงและการเรียงลำดับ


5

ดัชนีดูเหมือนจะค่อนข้างดีสำหรับการค้นหาและฉันไม่แน่ใจว่าทำไมเครื่องมือเพิ่มประสิทธิภาพจึงไม่ได้ถูกเลือก (สถิติการแบ่งพาร์ติชันหรือไม่ จำกัด สีฟ้าไม่เป็นความคิดจริง ๆ )

แต่ดัชนีที่กรองจะดียิ่งขึ้นสำหรับการสืบค้นเฉพาะหากค่า> 0นั้นเป็นค่าคงที่และจะไม่เปลี่ยนจากการดำเนินการค้นหาแบบหนึ่งเป็นอีกแบบ:

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

มีความแตกต่างสองประการระหว่างดัชนีที่คุณมีโดยที่OperationalSecondsคือคอลัมน์ที่ 3 และดัชนีที่กรอง:

  • ก่อนอื่นดัชนีที่กรองจะมีขนาดเล็กลงทั้งในความกว้าง (แคบกว่า) และในจำนวนแถว
    สิ่งนี้ทำให้ดัชนีที่กรองมีประสิทธิภาพมากขึ้นโดยทั่วไปเนื่องจาก SQL Server ต้องการพื้นที่น้อยกว่าเพื่อเก็บไว้ในหน่วยความจำ

  • ประการที่สองและนี่เป็นสิ่งที่ลึกซึ้งยิ่งขึ้นและสำคัญสำหรับแบบสอบถามคือมีแถวที่ตรงกับตัวกรองที่ใช้ในแบบสอบถาม สิ่งนี้อาจมีความสำคัญอย่างยิ่งขึ้นอยู่กับค่าของคอลัมน์ที่ 3 นี้
    ตัวอย่างเช่นชุดพารามิเตอร์เฉพาะสำหรับMachineryIdและDateRecordedอาจให้ผล 1,000 แถว หากแถวทั้งหมดหรือเกือบทั้งหมดตรงกับ(OperationalSeconds > 0)ตัวกรองดัชนีทั้งสองจะทำงานได้ดี แต่ถ้าแถวที่ตรงกับตัวกรองมีน้อยมาก (หรือเพียงแค่แถวสุดท้ายหรือไม่มีเลย) ดัชนีแรกจะต้องผ่านมากหรือ 1,000 แถวทั้งหมดจนกว่าจะพบการแข่งขัน ดัชนีที่ถูกกรองในอีกด้านหนึ่งต้องการค้นหาแถวที่ตรงกันเท่านั้น (หรือส่งคืน 0 แถว) เนื่องจากเก็บเฉพาะแถวที่ตรงกับตัวกรองเท่านั้น


1
การเพิ่มดัชนีทำให้ข้อความค้นหามีประสิทธิภาพมากขึ้นหรือไม่
ypercubeᵀᴹ

ไม่ใช่ฐานข้อมูลการจัดเตรียม (มันต้องการข้อมูลเพิ่มเติมเพื่อทำการทดสอบอย่างถูกต้อง) ฉันยังไม่ได้ลองใช้มันในขณะนี้ดัชนีใหม่ใช้เวลากว่าหนึ่งชั่วโมงในการสร้างฐานข้อมูลนั้น ฉันค่อนข้างลังเลที่จะทำอะไรกับฐานข้อมูลสดของเราเพราะมันทำงานช้าแล้ว เราต้องการระบบที่ดีกว่าสำหรับการโคลนนิ่งการแสดงสดของเรา
Andrew Williamson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.