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

दिलचस्प विशेषताएं हैं:
- नोड 0 में शीर्ष 100 पर लौटी पंक्तियों को सीमित करता है। यह ऑप्टिमाइज़र के लिए एक पंक्ति लक्ष्य भी निर्धारित करता है, इसलिए योजना में इसके नीचे की सभी चीज़ों को पहले 100 पंक्तियों को जल्दी से वापस करने के लिए चुना जाता है।
- नोड 4 पर स्कैन तालिका से पंक्तियों को ढूंढता है जहां
Start_Timeशून्य नहीं है, State3 या 4 है, और Operation_Typeसूचीबद्ध मूल्यों में से एक है। तालिका एक बार पूरी तरह से स्कैन की गई है, जिसमें प्रत्येक पंक्ति में उल्लिखित विधेय के खिलाफ परीक्षण किया गया है। केवल सभी परीक्षण पास करने वाली पंक्तियाँ क्रमबद्ध रूप से प्रवाहित होती हैं। आशावादी का अनुमान है कि 38,283 पंक्तियाँ योग्य होंगी।
- नोड 3 पर सॉर्ट नोड 4 पर स्कैन से सभी पंक्तियों का उपभोग करता है, और क्रम में उन्हें सॉर्ट करता है
Start_Time DESC। यह क्वेरी द्वारा अनुरोधित अंतिम प्रस्तुति आदेश है।
- ऑप्टिमाइज़र का अनुमान है कि 100 पंक्तियों को वापस करने की पूरी योजना के लिए 93 पंक्तियों (वास्तव में 93.2791) को सॉर्ट से पढ़ना होगा (जॉइन के अपेक्षित प्रभाव के लिए लेखांकन)।
- नेस्टेड लूप्स नोड 2 में शामिल होते हैं, इसके आंतरिक इनपुट (निचली शाखा) को 94 बार (वास्तव में 94.2791) निष्पादित करने की उम्मीद है। तकनीकी कारणों से नोड 1 पर स्टॉप समानांतरवाद विनिमय द्वारा अतिरिक्त पंक्ति की आवश्यकता है।
- नोड 5 पर स्कैन प्रत्येक पुनरावृत्ति पर तालिका को पूरी तरह से स्कैन करता है। यह उन पंक्तियों को ढूँढता है जहाँ
Start_Timeअशक्त नहीं है और State3 या 4 है। यह अनुमान है कि प्रत्येक पुनरावृत्ति पर 400,875 पंक्तियों का उत्पादन किया जाएगा। 94.2791 पुनरावृत्तियों पर, कुल पंक्तियों की संख्या लगभग 38 मिलियन है।
- नेस्टेड लूप्स नोड 2 में शामिल हो जाते हैं, शामिल होने वाले विधेय पर भी लागू होते हैं। यह जाँचता है कि
Operation_Typeमैच, कि Start_Timeनोड 4 Start_Timeसे नोड 5 से कम है , कि Start_Timeनोड 5 Finish_Timeसे नोड 4 से कम है , और दोनों Idमान मेल नहीं खाते हैं।
- नोड 1 पर इकट्ठा होने वाली धाराएं (समानांतरवाद विनिमय बंद करो) प्रत्येक थ्रेड से आदेशित धाराओं को तब तक मिला देती हैं जब तक कि 100 पंक्तियों का उत्पादन नहीं हो जाता। कई धाराओं में मर्ज का क्रम-संरक्षण प्रकृति चरण 5 में उल्लिखित अतिरिक्त पंक्ति की आवश्यकता है।
महान अक्षमता स्पष्ट रूप से 6 और 7 से ऊपर के चरणों में है। प्रत्येक पुनरावृत्ति के लिए नोड 5 पर तालिका को पूरी तरह से स्कैन करना केवल थोड़ा उचित है यदि यह केवल 94 बार होता है जैसा कि आशावादी भविष्यवाणी करता है। नोड 2 पर तुलना का ~ 38 मिलियन प्रति पंक्ति सेट भी एक बड़ी लागत है।
महत्वपूर्ण रूप से, 93/94 पंक्ति पंक्ति लक्ष्य अनुमान भी गलत होने की संभावना है, क्योंकि यह मूल्यों के वितरण पर निर्भर करता है। आशावादी अधिक विस्तृत जानकारी के अभाव में समान वितरण को मानता है। सरल शब्दों में, इसका मतलब है कि अगर तालिका में 1% पंक्तियों को योग्य होने की उम्मीद है, तो अनुकूलक का कारण है कि 1 मिलान पंक्ति को खोजने के लिए, इसे 100 पंक्तियों को पढ़ने की आवश्यकता है।
यदि आप इस प्रश्न को पूरा करने के लिए दौड़े (जो कि बहुत लंबा समय लग सकता है), तो आप सबसे अधिक संभावना यह पाएंगे कि अंत में 100 पंक्तियों का उत्पादन करने के लिए 93/94 पंक्तियों से अधिक कई को सॉर्ट से पढ़ा जाना था। सबसे खराब स्थिति में, 100 वीं पंक्ति क्रमबद्ध से अंतिम पंक्ति का उपयोग कर पाया की जाएगी। नोड 4 पर ऑप्टिमाइज़र के अनुमान को सही मानते हुए, इसका मतलब है कि कुल 5 बिलियन पंक्तियों जैसे कुछ के लिए 5, 38,284 बार नोड पर स्कैन चलाना । यह अधिक हो सकता है अगर स्कैन का अनुमान भी बंद हो।
इस निष्पादन योजना में एक लापता सूचकांक चेतावनी भी शामिल है:
/*
The Query Processor estimates that implementing the following index
could improve the query cost by 72.7096%.
WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([Operation_Type],[State],[Start_Time])
INCLUDE ([Id],[Parameters])
आशावादी आपको इस तथ्य से सावधान कर रहा है कि तालिका में एक सूचकांक जोड़ने से प्रदर्शन में सुधार होगा।
अतिरिक्त कॉलम के बिना योजना बनाएं

यह मूल रूप से पिछले एक के समान सटीक योजना है, जिसमें नोड 6 पर सूचकांक स्पूल और नोड 5 पर फ़िल्टर के अलावा 5. महत्वपूर्ण अंतर हैं:
- नोड 6 पर सूचकांक स्पूल एक एगर स्पूल है। यह नीचे दिए गए स्कैन के परिणाम का उत्सुकता से उपभोग करता है, और एक गैर-कुंजी कॉलम के रूप में , पर
Operation_Typeऔर एक अस्थायी सूचकांक बनाता है ।Start_TimeId
- नेस्टेड लूप्स को नोड 2 में शामिल करें अब एक इंडेक्स जॉइन है। कोई शामिल हो विधेय यहाँ मूल्यांकन किया जाता है, बजाय प्रति-यात्रा की वर्तमान मूल्यों
Operation_Type, Start_Time, Finish_Time, और Idनोड 4 में स्कैन से बाहरी संदर्भ के रूप में आंतरिक साइड शाखा को पारित कर रहे हैं।
- नोड 7 पर स्कैन केवल एक बार किया जाता है।
- नोड 6 पर इंडेक्स स्पूल अस्थायी इंडेक्स से पंक्तियों की तलाश करता है जहां
Operation_Typeवर्तमान बाहरी संदर्भ मूल्य से मेल खाता Start_Timeहै, Start_Timeऔर Finish_Timeबाहरी और संदर्भ द्वारा परिभाषित सीमा में है ।
Idवर्तमान बाहरी संदर्भ मूल्य के खिलाफ असमानता के लिए अनुक्रमणिका स्पूल से नोड 5 परीक्षण मूल्यों पर फ़िल्टर Id।
प्रमुख सुधार हैं:
- आंतरिक साइड स्कैन केवल एक बार किया जाता है
- एक शामिल कॉलम के रूप में (
Operation_Type, Start_Time) पर एक अस्थायी सूचकांक Idएक सूचकांक नेस्टेड छोरों को शामिल करने की अनुमति देता है। सूचकांक का उपयोग प्रत्येक बार पूरी तालिका को स्कैन करने के बजाय प्रत्येक पुनरावृत्ति पर मिलान पंक्तियों की तलाश करने के लिए किया जाता है।
पहले की तरह, ऑप्टिमाइज़र में एक लापता सूचकांक के बारे में चेतावनी शामिल है:
/*
The Query Processor estimates that implementing the following index
could improve the query cost by 24.1475%.
WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([State],[Start_Time])
INCLUDE ([Id],[Operation_Type])
GO
निष्कर्ष
अतिरिक्त कॉलम के बिना योजना तेज है क्योंकि आशावादी ने आपके लिए एक अस्थायी सूचकांक बनाने का विकल्प चुना है।
अतिरिक्त स्तंभों के साथ योजना अस्थायी सूचकांक को बनाने के लिए अधिक महंगी होगी। [Parameters] स्तंभ है nvarchar(2000)जो सूचकांक की प्रत्येक पंक्ति को 4000 बाइट तक जोड़ना होगा। अतिरिक्त लागत अनुकूलनकर्ता को यह समझाने के लिए पर्याप्त है कि प्रत्येक निष्पादन पर अस्थायी सूचकांक का निर्माण खुद के लिए भुगतान नहीं करेगा।
आशावादी दोनों मामलों में चेतावनी देते हैं कि एक स्थायी सूचकांक एक बेहतर समाधान होगा। सूचकांक की आदर्श रचना आपके व्यापक कार्यभार पर निर्भर करती है। इस विशेष क्वेरी के लिए, सुझाए गए इंडेक्स एक उचित प्रारंभिक बिंदु हैं, लेकिन आपको इसमें शामिल लाभों और लागतों को समझना चाहिए।
सिफ़ारिश करना
इस क्वेरी के लिए संभावित अनुक्रमित की एक विस्तृत श्रृंखला लाभदायक होगी। महत्वपूर्ण उपाय यह है कि किसी प्रकार के गैर-अनुक्रमित सूचकांक की आवश्यकता है। दी गई जानकारी से, मेरी राय में एक उचित सूचकांक होगा:
CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time);
मुझे क्वेरी को थोड़ा बेहतर ढंग से व्यवस्थित करने के लिए भी लुभाया जाएगा, और [Parameters]शीर्ष 100 पंक्तियों के पाए जाने के बाद ( Idकुंजी के रूप में) का उपयोग करते हुए क्लस्टर इंडेक्स में विस्तृत कॉलम देखने में देरी होगी :
SELECT TOP (100)
BTQ1.id,
BTQ2.id,
BTQ3.[Parameters],
BTQ4.[Parameters]
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
ON BTQ2.Operation_Type = BTQ1.Operation_Type
AND BTQ2.Start_Time > BTQ1.Start_Time
AND BTQ2.Start_Time < BTQ1.Finish_Time
AND BTQ2.id != BTQ1.id
-- Look up the [Parameters] values
JOIN dbo.Batch_Tasks_Queue AS BTQ3
ON BTQ3.Id = BTQ1.Id
JOIN dbo.Batch_Tasks_Queue AS BTQ4
ON BTQ4.Id = BTQ2.Id
WHERE
BTQ1.[State] IN (3, 4)
AND BTQ2.[State] IN (3, 4)
AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
-- These predicates are not strictly needed
AND BTQ1.Start_Time IS NOT NULL
AND BTQ2.Start_Time IS NOT NULL
ORDER BY
BTQ1.Start_Time DESC;
जहां [Parameters]कॉलम की जरूरत नहीं है, वहां क्वेरी को सरल बनाया जा सकता है:
SELECT TOP (100)
BTQ1.id,
BTQ2.id
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
ON BTQ2.Operation_Type = BTQ1.Operation_Type
AND BTQ2.Start_Time > BTQ1.Start_Time
AND BTQ2.Start_Time < BTQ1.Finish_Time
AND BTQ2.id != BTQ1.id
WHERE
BTQ1.[State] IN (3, 4)
AND BTQ2.[State] IN (3, 4)
AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ1.Start_Time IS NOT NULL
AND BTQ2.Start_Time IS NOT NULL
ORDER BY
BTQ1.Start_Time DESC;
FORCESEEKसंकेत सुनिश्चित अनुकूलक चुनता है एक अनुक्रमित नेस्टेड छोरों की योजना में मदद करने के लिए नहीं है (वहाँ एक हैश या (अनेक-अनेक) मर्ज अन्यथा शामिल होने का चयन करने के अनुकूलक के लिए एक लागत आधारित प्रलोभन है, जो अच्छी तरह से इस प्रकार के काम करने के लिए नहीं जाता है व्यवहार में क्वेरी। दोनों बड़े अवशेषों के साथ समाप्त होते हैं; हैश के मामले में प्रति बाल्टी कई आइटम, और मर्ज के लिए कई रीवाइंड)।
विकल्प
यदि क्वेरी (इसके विशिष्ट मान सहित) विशेष रूप से पढ़ने के प्रदर्शन के लिए महत्वपूर्ण थी , तो मैं इसके बजाय दो फ़िल्टर किए गए इंडेक्सों पर विचार करूंगा:
CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time)
WHERE
Start_Time IS NOT NULL
AND [State] IN (3, 4)
AND Operation_Type <> 23
AND Operation_Type <> 24
AND Operation_Type <> 25
AND Operation_Type <> 26
AND Operation_Type <> 27
AND Operation_Type <> 28
AND Operation_Type <> 30;
CREATE NONCLUSTERED INDEX i2
ON dbo.Batch_Tasks_Queue (Operation_Type, [State], Start_Time)
WHERE
Start_Time IS NOT NULL
AND [State] IN (3, 4)
AND Operation_Type <> 23
AND Operation_Type <> 24
AND Operation_Type <> 25
AND Operation_Type <> 26
AND Operation_Type <> 27
AND Operation_Type <> 28
AND Operation_Type <> 30;
उस क्वेरी के लिए जिसे [Parameters]स्तंभ की आवश्यकता नहीं है , फ़िल्टर किए गए अनुक्रमित का उपयोग करके अनुमानित योजना है:

सूचकांक स्कैन किसी भी अतिरिक्त विधेयकों का मूल्यांकन किए बिना सभी योग्य पंक्तियों को स्वचालित रूप से लौटाता है। इंडेक्स नेस्टेड लूप्स के प्रत्येक पुनरावृत्ति में शामिल होने के लिए, इंडेक्स की तलाश में दो ऑपरेशन करना चाहते हैं:
- एक उपसर्ग मैच पर
Operation_Typeऔर State= 3, फिर Start_Timeमूल्यों की श्रेणी की मांग , अवशिष्ट Idअसमानता पर विधेय ।
- एक उपसर्ग मैच
Operation_Typeऔर State= 4 पर, फिर Start_Timeमूल्यों की श्रेणी की मांग , अवशिष्ट Idअसमानता पर विधेय ।
जहां [Parameters]कॉलम की आवश्यकता होती है, क्वेरी प्लान बस प्रत्येक तालिका के लिए अधिकतम 100 सिंगलटन लुकअप जोड़ता है:

अंतिम नोट के रूप में, आपको numericजहां लागू हो वहां अंतर्निहित मानक पूर्णांक प्रकारों का उपयोग करने पर विचार करना चाहिए ।