मेरे सूचकांक का चयन शीर्ष में क्यों नहीं किया जा रहा है?


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बार एक मशीन के लिए रिकॉर्ड करना चाहता हूं )। हालाँकि, जो प्रश्न मैं हाथ से लिख रहा हूं, वे सभी एक अच्छा 10 - 100 गुना तेजी से चल रहे हैं जो कि EntityFramework ने उत्पन्न किया है, इसलिए मैं केवल एक संग्रहीत प्रक्रिया बनाने जा रहा हूं।


1
हाय @AndrewWilliamson, यह एक आँकड़े मुद्दा हो सकता है। यदि आप अप्रत्याशित योजना से वास्तविक योजना को देखते हैं, तो पंक्तियों की अनुमानित संख्या 1.22 है और वास्तविक 19039 है। इसके बदले में महत्वपूर्ण लुकअप होता है जिसे यू बाद में योजना में देखते हैं। क्या आपने आँकड़ों को अपडेट करने की कोशिश की है? यदि नहीं, तो स्टेजिंग डेटाबेस पर पूर्ण स्कैन के साथ प्रयास करें।
jesijesi

जवाबों:


21

यदि मैं सर्वर को यह तय करने देता हूं कि किस इंडेक्स का उपयोग करना है, तो वह चुनता है IX_MachineryId, और इसमें एक मिनट तक का समय लगता है।

उस इंडेक्स का विभाजन नहीं किया जाता है, इसलिए ऑप्टिमाइज़र पहचानता है कि इसका उपयोग बिना छांटे क्वेरी में निर्दिष्ट ऑर्डर देने के लिए किया जा सकता है। एक गैर-अद्वितीय गैर-अनुक्रमित सूचकांक के रूप में, इसमें उपकुंजियों के रूप में क्लस्टर किए गए सूचकांक की कुंजी भी होती है, इसलिए सूचकांक का उपयोग MachineryIdऔर DateRecordedसीमा की तलाश में किया जा सकता है :

इंडेक्स सीक

सूचकांक में शामिल नहीं है OperationalSeconds, इसलिए योजना को परीक्षण करने के लिए (विभाजित) क्लस्टर किए गए सूचकांक में प्रति पंक्ति मान को देखना होगा OperationalSeconds > 0:

देखो

ऑप्टिमाइज़र का अनुमान है कि एक पंक्ति को गैर-अनुक्रमित सूचकांक से पढ़ने की आवश्यकता होगी और संतुष्ट करने के लिए ऊपर देखा जाएगा TOP (1)। यह गणना पंक्ति लक्ष्य (जल्दी एक पंक्ति खोजें) पर आधारित है, और मूल्यों का एक समान वितरण मानती है।

वास्तविक योजना से, हम देख सकते हैं कि 1 पंक्ति का अनुमान गलत है। वास्तव में, 19,039 पंक्तियों को यह पता लगाने के लिए संसाधित किया जाना चाहिए कि कोई भी पंक्तियां क्वेरी की शर्तों को पूरा नहीं करती हैं। पंक्ति लक्ष्य अनुकूलन के लिए यह सबसे खराब स्थिति है (1 पंक्ति अनुमानित, वास्तव में आवश्यक सभी पंक्तियाँ):

वास्तविक / अनुमान

आप पंक्ति के लक्ष्यों को ट्रेस ध्वज 4138 के साथ अक्षम कर सकते हैं । यह SQL सर्वर में एक अलग योजना चुनने का सबसे अधिक संभावना परिणाम होगा, संभवतः एक जिसे आपने मजबूर किया था। किसी भी मामले में, सूचकांक IX_MachineryIdको शामिल करके अधिक इष्टतम बनाया जा सकता है OperationalSeconds

गैर-संरेखित गैर-अनुक्रमित अनुक्रमणिकाएं होना काफी असामान्य है (बेस तालिका से अलग तरीके से विभाजित किए गए अनुक्रमणिका, जिसमें बिल्कुल भी शामिल नहीं है)।

यह वास्तव में मुझे सुझाव देता है कि मैंने सूचकांक को सही बनाया है, और सर्वर सिर्फ एक बुरा निर्णय ले रहा है। क्यों?

हमेशा की तरह, ऑप्टिमाइज़र सबसे सस्ती योजना का चयन करता है जिसे वह मानता है।

की अनुमानित लागत IX_MachineryIdयोजना 0.01 लागत इकाइयाँ हैं, (गलत) पंक्ति लक्ष्य धारणा के आधार पर कि एक पंक्ति का परीक्षण किया जाएगा और वापस लौटाया जाएगा।

IX_MachineryId_DateRecordedयोजना की अनुमानित लागत बहुत अधिक है, 0.27 इकाइयों पर, ज्यादातर क्योंकि यह सूचकांक से 5,515 पंक्तियों को पढ़ने की उम्मीद करता है, उन्हें क्रमबद्ध करता है, और सबसे कम (द्वारा DateRecorded) वाले को वापस करता है:

शीर्ष एन सॉर्ट

यह सूचकांक विभाजित है, और DateRecordedसीधे क्रम में पंक्तियों को नहीं लौटा सकता है (बाद में देखें)। यह प्रत्येक विभाजन के भीतरMachineryId और DateRecordedसीमा की तलाश कर सकता है , लेकिन एक क्रमबद्धता आवश्यक है:

विभाजन की तलाश

यदि इस सूचकांक को विभाजित नहीं किया गया था, तो एक प्रकार की आवश्यकता नहीं होगी, और यह अतिरिक्त शामिल कॉलम के साथ अन्य (अप्रकाशित) सूचकांक के समान होगा। एक गैर-फ़िल्टर किए गए फ़िल्टर इंडेक्स अभी भी थोड़े अधिक कुशल होंगे।


ताकि आप स्रोत क्वेरी अद्यतन करना चाहिए डेटा प्रकार के @Fromऔर @Toमानकों से मेलDateRecorded स्तंभ ( datetime)। इस समय, SQL सर्वर रन टाइम पर मिसमैच के कारण डायनामिक रेंज की गणना कर रहा है (मर्ज इंटरवल ऑपरेटर और उसके सबट्री का उपयोग करके):

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

यह रूपांतरण ऑप्टिमाइज़र को आरोही विभाजन आईडी ( DateRecordedआरोही क्रम में मूल्यों की एक सीमा को कवर ) और असमानता पर निर्भर करता है के बीच के संबंध के बारे में सही तरीके से रोकता है DateRecorded

विभाजन आईडी एक विभाजित सूचकांक के लिए एक अंतर्निहित प्रमुख कुंजी है। आम तौर पर, आशावादी यह देख सकता है कि विभाजन आईडी (जहां आरोही आईडी मैप्स को आरोही, असंतुष्ट मानकर 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तीसरा कॉलम है और फ़िल्टर किया गया इंडेक्स है:

  • पहले फ़िल्टर किया गया इंडेक्स चौड़ाई (संकरा) और पंक्तियों की संख्या में छोटा होता है।
    यह फ़िल्टर किए गए इंडेक्स को सामान्य रूप से अधिक कुशल बनाता है क्योंकि SQL सर्वर को इसे स्मृति में रखने के लिए कम स्थान की आवश्यकता होती है।

  • दूसरा और क्वेरी के लिए यह अधिक सूक्ष्म और महत्वपूर्ण है कि इसमें केवल पंक्तियाँ हैं जो क्वेरी में उपयोग किए गए फ़िल्टर से मेल खाती हैं। यह अत्यंत महत्वपूर्ण हो सकता है, जो इस तृतीय स्तंभ के मूल्यों पर निर्भर करता है।
    उदाहरण के लिए, मापदंडों का एक विशिष्ट सेट MachineryIdऔर DateRecorded1000 पंक्तियों का उत्पादन कर सकता है। यदि इन सभी पंक्तियों के सभी या लगभग सभी (OperationalSeconds > 0)फ़िल्टर से मेल खाते हैं , तो दोनों अनुक्रमित अच्छी तरह से व्यवहार करेंगे। लेकिन यदि फ़िल्टर से मेल खाती पंक्तियाँ बहुत कम हैं (या बस अंतिम एक या कोई भी नहीं), तो पहले सूचकांक को बहुत या उन सभी 1000 पंक्तियों से गुजरना होगा, जब तक कि यह एक मैच नहीं पाता। दूसरी ओर फ़िल्टर किए गए इंडेक्स को केवल एक मिलान पंक्ति (या 0 पंक्तियों को वापस करने) की आवश्यकता होती है क्योंकि फ़िल्टर से मेल खाने वाली पंक्तियाँ संग्रहीत होती हैं।


1
क्या सूचकांक को जोड़ने से क्वेरी अधिक कुशल हो गई है?
ypercube y

स्टेजिंग डेटाबेस के लिए नहीं (इसे ठीक से परीक्षण करने के लिए वास्तव में इसमें अधिक डेटा की आवश्यकता होती है), मैंने इसे अभी तक लाइव करने की कोशिश नहीं की है, नए इंडेक्स को उस पर बनाने के लिए एक घंटे से अधिक समय लगता है। मुझे हमारे लाइव डेटाबेस के लिए कुछ भी करने में बहुत संकोच हो रहा है, क्योंकि यह पहले से ही धीरे-धीरे चल रहा है। हमें अपने लाइव का मंचन करने के लिए एक बेहतर प्रणाली की आवश्यकता है।
एंड्रयू विलियमसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.