एक शीर्ष 1 नाटकीय रूप से खराब प्रदर्शन को क्यों जोड़ता है?


39

मेरे पास काफी सरल क्वेरी है

SELECT TOP 1 dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

वह मुझे भयानक प्रदर्शन दे रहा है (जैसे कभी इसके खत्म होने का इंतजार करने की जहमत नहीं उठाई गई)। क्वेरी योजना इस तरह दिखती है:

यहाँ छवि विवरण दर्ज करें

हालाँकि अगर मैं हटाता हूँ तो मुझे TOP 1एक योजना मिलती है जो इस तरह दिखती है और यह 1-2 सेकंड में चलती है:

यहाँ छवि विवरण दर्ज करें

नीचे दिए गए PK और अनुक्रमण को ठीक करें।

तथ्य यह है कि TOP 1क्वेरी योजना को बदल देने से मुझे कोई आश्चर्य नहीं होता, मैं बस थोड़ा आश्चर्यचकित हूं कि यह इसे इतना बदतर बना देता है।

नोट: मैंने इस पोस्ट के परिणामों को पढ़ा है और Row Goalआदि की अवधारणा को समझा है । मैं उत्सुक हूं कि मैं क्वेरी को बदलने के बारे में कैसे जा सकता हूं ताकि यह बेहतर योजना का उपयोग करे। वर्तमान में मैं डेटा को एक टेम्‍प टेबल में डंप कर रहा हूँ और फिर इसकी पहली पंक्ति को खींच रहा हूँ। मैं सोच रहा हूं कि क्या कोई बेहतर तरीका है।

इस तथ्य के बाद इसे पढ़ने वाले लोगों के लिए संपादित करें जानकारी के कुछ अतिरिक्त टुकड़े हैं।

  • Document_Queue - PK / CI D_ID है और इसमें ~ 5k पंक्तियाँ हैं।
  • पत्राचार_जोरनाल - पीके / सीआई FILE_NUMBER, CORRESPONDENCE_ID है और इसमें ~ 1.4 मील की पंक्तियाँ हैं।

जब मैंने शुरू किया तो कोई अन्य सूचकांक नहीं थे। मैं पत्राचार_जोरनाल (Document_Id, File_Number) पर एक के साथ समाप्त हुआ


1
क्या आपके पास एक विदेशी कुंजी बाधा है DOCUMENT_IDजो दो तालिकाओं के बीच संबंध को लागू करती है (या हर रिकॉर्ड का CORRESPONDENCE_JOURNALमिलान रिकॉर्ड में है DOCUMENT_QUEUE)?
डेनियल हट्मैचर

जवाबों:


28

हैश ज्वाइन करने के लिए मजबूर करें *

SELECT TOP 1 
       dc.DOCUMENT_ID,
       dc.COPIES,
       dc.REQUESTOR,
       dc.D_ID,
       cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
INNER HASH JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
       AND dc.QUEUE_DATE <= GETDATE()
       AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

ऑप्टिमाइज़र ने शायद सोचा था कि शीर्ष 1 के साथ एक लूप बेहतर होने वाला है और उस तरह की समझ में आता है लेकिन वास्तव में यह यहां काम नहीं करता है। यहां केवल एक अनुमान है, लेकिन शायद उस स्पूल की अनुमानित लागत बंद थी - यह TEMPDB का उपयोग करता है - आपके पास TEMPDB खराब प्रदर्शन कर सकता है।


* जुड़ने के संकेत से सावधान रहें , क्योंकि वे क्वेरी में तालिकाओं के लिखित क्रम (जैसे कि OPTION (FORCE ORDER)निर्दिष्ट किया गया था) से मेल खाने के लिए योजना तालिका अभिगम आदेश को बाध्य करते हैं । प्रलेखन लिंक से:

BOL अर्क

यह उदाहरण में कोई अवांछनीय प्रभाव उत्पन्न नहीं कर सकता है, लेकिन सामान्य तौर पर, यह बहुत अच्छी तरह से हो सकता है। FORCE ORDER(निहित या स्पष्ट) एक बहुत शक्तिशाली संकेत है जो लागू करने के आदेश से परे है; यह आंशिक रूप से एकत्रीकरण और पुन: व्यवस्थित करने सहित, ऑप्टिमाइज़र तकनीकों की एक विस्तृत श्रृंखला को रोकता है।

एक OPTION (HASH JOIN) क्वेरी संकेत उपयुक्त मामलों में कम दखल हो सकता है, क्योंकि यह मतलब नहीं है FORCE ORDER। हालाँकि, यह क्वेरी में सभी जॉइन पर लागू होता है। अन्य समाधान उपलब्ध हैं।


1
लगता है कि सही उत्तर और इसके और सरल योजना के बीच का एकमात्र अंतर सामने की ओर एक अतिरिक्त क्रमबद्ध था।
केनेथ फिशर

3
यकीन नहीं होता कि मुझे यह जवाब पसंद है। सम्मिलित हों संकेत बहुत आक्रामक हैं। कुछ सरल अनुक्रमण परिवर्तनों को पहले प्रयास किया जाना चाहिए, उदाहरण के लिए दिनांक स्तंभ पर अनुक्रमणिका।
यूआर

@usr यह एक साधारण पीके है जो एक सेकंड से भी कम समय में चलता है। बहुत ही सुरक्षित शर्त यहाँ।
पापाराज़ो

4
एक हैश ज्वाइन करने के लिए, आप बड़ी टेबल का एक स्कैन करने के लिए मजबूर कर रहे हैं। बेहतर विकल्प हैं।
रॉब फ़र्ले

30

चूँकि आपको सही योजना मिलती है ORDER BY, हो सकता है कि आप अपने खुद के TOPऑपरेटर का रोल कर सकें ?

SELECT DOCUMENT_ID, COPIES, REQUESTOR, D_ID, FILE_NUMBER
FROM (
    SELECT dc.DOCUMENT_ID,
           dc.COPIES,
           dc.REQUESTOR,
           dc.D_ID,
           cj.FILE_NUMBER,
           ROW_NUMBER() OVER (ORDER BY cj.FILE_NUMBER) AS _rownum
    FROM DOCUMENT_QUEUE dc
    INNER JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
    WHERE dc.QUEUE_DATE <= GETDATE()
      AND dc.PRINT_LOCATION = 2
) AS sub
WHERE _rownum=1;

मेरे दिमाग में, ROW_NUMBER()उपरोक्त के लिए क्वेरी योजना उसी तरह होनी चाहिए जैसे कि आपने ए ORDER BY। क्वेरी प्लान में अब एक सेगमेंट, अनुक्रम परियोजना और अंत में एक फ़िल्टर ऑपरेटर होना चाहिए, बाकी को आपकी अच्छी योजना की तरह दिखना चाहिए।


3
वास्तव में जब इसने शीर्ष ऑपरेटर (और अन्य सामानों का एक गुच्छा (एक अनुक्रम परियोजना, खंड, और सॉर्ट)) दिया था, तब भी यह सबसेकंड चला। मैं @frisbee को सही जवाब देने जा रहा हूं, हालांकि जब से वह पहले था और यह सरल है। हालांकि महान जवाब।
केनेथ फिशर

10
@ केनेथफिशर, फ्रिसबी का जवाब सरल है, लेकिन जिस तरह से एक स्लेजहैमर एक मानक कील वाले हथौड़ा की तुलना में अधिक सरलता से नाखून खत्म करता है। यह बहुत अधिक जोखिम के साथ आता है, खासकर अगर लंबी दौड़ के लिए जगह छोड़ दी जाए। मैं इस तरह के संकेत का उपयोग नहीं करेगा कि परीक्षण या शायद को छोड़कर, एक फ्रिंज अपवाद हो सकता है।
स्टीव मांगमेली

@SteveMangiameli इस विशेष मामले में केवल एक ही शामिल है इसलिए कई चिंताएं दूर हो जाती हैं। मैं एक संकेत (या क्वेरी संकेत) का उपयोग करने के जोखिमों से अवगत हूं, मुझे लगता है कि यह इस मामले में उचित है।
केनेथ फिशर

5
@KennethFisher Imo, क्वेरी संकेत का मुख्य जोखिम यह है कि जैसे-जैसे आपका डेटा बढ़ता है या बदलता है, आपके द्वारा लागू की जाने वाली क्वेरी योजना उस सिस्टम से अपने आप को प्राप्त करने की तुलना में बदतर हो सकती है। आपने पहले ही देखा है कि योजना में एक छोटी सी गलती प्रदर्शन को कैसे प्रभावित कर सकती है। उत्पादन में एक संकेत का उपयोग करते हुए घोषित किया जाता है, "मुझे पता है कि यह योजना हमेशा, हमेशा सर्वश्रेष्ठ रहेगी क्योंकि मैं पूरी तरह से योजनाकार को समझता हूं और मेरा डेटा उत्पादन में इस क्वेरी के जीवनकाल में कैसे व्यवहार करेगा।" मैं एक प्रश्न के बारे में कभी आश्वस्त नहीं रहा।
jpmc26

29

संपादित करें: +1 इस स्थिति में काम करता है क्योंकि यह पता चलता है कि FILE_NUMBERपूर्णांक का शून्य-गद्देदार स्ट्रिंग संस्करण है। तार के लिए यहाँ एक बेहतर समाधान है ''(खाली स्ट्रिंग) जोड़ना, जैसा कि एक मूल्य को लागू करना आदेश को प्रभावित कर सकता है, या संख्याओं के लिए कुछ जोड़ सकता है जो एक स्थिर है लेकिन इसमें गैर-नियतात्मक फ़ंक्शन शामिल है, जैसे sign(rand()+1)। 'अभी भी' को तोड़ने का विचार यहाँ मान्य है, यह सिर्फ इतना है कि मेरा तरीका आदर्श नहीं था।

+1

नहीं, मेरा मतलब यह नहीं है कि मैं किसी भी चीज से सहमत हूं, मेरा मतलब है कि समाधान के रूप में। यदि आप अपनी क्वेरी बदलते हैं ORDER BY cj.FILE_NUMBER + 1तो TOP 1वसीयत अलग तरह से व्यवहार करेगी।

आप एक आदेशित क्वेरी के लिए जगह में छोटे पंक्ति के लक्ष्य के साथ देखते हैं, सिस्टम क्रमबद्ध डेटा होने से बचने के लिए, क्रम में डेटा का उपभोग करने की कोशिश करेगा। यह भी हैश टेबल बनाने से बचता है, यह अनुमान लगाता है कि शायद उस पहली पंक्ति को खोजने के लिए बहुत अधिक काम नहीं करना है। आपके मामले में, यह गलत है - उन तीरों की मोटाई से, ऐसा लगता है कि एक एकल मैच खोजने के लिए बहुत सारे डेटा का उपभोग करना है।

उन तीरों की मोटाई बताती है कि आपकी DOCUMENT_QUEUE(DQ) तालिका आपकी CORRESPONDENCE_JOURNAL(CJ) तालिका से बहुत छोटी है । और यह कि सबसे अच्छी योजना वास्तव में डीक्यू पंक्तियों के माध्यम से जांचना होगी जब तक कि एक सीजे पंक्ति नहीं मिलती है। वास्तव में, यह वही है जो क्वेरी ऑप्टिमाइज़र (QO) करेगा अगर इसमें यह pesky नहीं था ORDER BY, जो कि CJ पर कवरिंग इंडेक्स द्वारा अच्छी तरह से समर्थित है।

इसलिए यदि आप ORDER BYपूरी तरह से गिर गए हैं , तो मुझे उम्मीद है कि आपको एक योजना मिलेगी जिसमें एक नेस्टेड लूप शामिल है, जो DQ में पंक्तियों से अधिक पुनरावृत्ति करता है, यह सुनिश्चित करने के लिए CJ में मांग करता है कि पंक्ति मौजूद है। और इसके साथ ही TOP 1, एक पंक्ति को खींचने के बाद यह बंद हो जाएगा।

लेकिन अगर आपको वास्तव में FILE_NUMBERक्रम में पहली पंक्ति की आवश्यकता है , तो आप सिस्टम को उस सूचकांक को अनदेखा करने में छल कर सकते हैं, जो (गलत तरीके से) ऐसा करने में मददगार लगता है, जिसे करके ORDER BY CJ.FILE_NUMBER+1- हम जानते हैं कि पहले जैसा ही क्रम बना रहेगा, लेकिन महत्वपूर्ण रूप से क्यूओ ऐसा नहीं करता। QO पूरे सेट आउट को प्राप्त करने पर ध्यान केंद्रित करेगा, ताकि एक टॉप एन सॉर्ट ऑपरेटर संतुष्ट हो सके। इस विधि से एक योजना तैयार करनी चाहिए जिसमें ऑर्डर करने के लिए मूल्य वर्क आउट करने के लिए एक कंपाइलर ऑपरेटर होता है, और पहली पंक्ति प्राप्त करने के लिए एक टॉप एन सॉर्ट ऑपरेटर होता है। लेकिन इन के दाईं ओर, आपको एक अच्छा नेस्टेड लूप देखना चाहिए, सीजे पर बहुत सारे सीक। और पंक्तियों की एक बड़ी तालिका के माध्यम से चलने से बेहतर प्रदर्शन जो कि DQ में कुछ भी मेल नहीं खाते हैं।

हैश मैच आवश्यक रूप से भयानक नहीं है, लेकिन यदि आप DQ से लौटने वाली पंक्तियों का सेट CJ से छोटा है (जैसा कि मैं यह होने की उम्मीद करूँगा), तो हैश मैच CJ के बहुत अधिक स्कैन होने वाला है जरूरत से ज्यादा।

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


7

मैंने इस पोस्ट के परिणामों को पढ़ा है और एक पंक्ति लक्ष्य आदि की अवधारणा को समझा है। मैं उत्सुक हूं कि कैसे मैं क्वेरी को बदलने के बारे में जा सकता हूं ताकि यह बेहतर योजना का उपयोग करे

OPTION (QUERYTRACEON 4138)अंतिम योजना के बारे में अत्यधिक निर्धारित किए बिना, केवल उस क्वेरी के लिए पंक्ति लक्ष्यों के प्रभाव को जोड़ना बंद करता है, और संभवतः सबसे सरल / सबसे सीधा तरीका होगा।

यदि इस संकेत को जोड़ने से आपको एक अनुमति त्रुटि (के लिए आवश्यक DBCC TRACEON) मिलती है , तो आप इसे योजना मार्गदर्शिका का उपयोग करके लागू कर सकते हैं:

का उपयोग करते हुए QUERYTRACEONयोजना गाइड में से spaghettidba

... या केवल एक संग्रहीत कार्यविधि का उपयोग करें:

अनुमतियाँ क्या QUERYTRACEONचाहिए? केंद्र लिटिल द्वारा


3

SQL सर्वर के नए संस्करण प्रश्नों से निपटने के लिए अलग (और यकीनन बेहतर) विकल्प प्रदान करते हैं, जो कि ऑप्टिमाइज़र को पंक्ति लक्ष्य ऑप्टिमाइज़ेशन लागू करने में सक्षम होने पर उप-प्रस्तुतिक प्रदर्शन मिलता है। SQL Server 2016 SP1 ने DISABLE_OPTIMIZER_ROWGOAL USE HINTट्रेस फ़्लैग 4138 के समान प्रभाव डाला। यदि आप उस संस्करण पर नहीं हैं, तो आप OPTIMIZE FORक्वेरी पंक्तियों का उपयोग करने के लिए केवल 1 के बजाय सभी पंक्तियों को वापस करने के लिए डिज़ाइन की गई क्वेरी योजना का उपयोग करने पर विचार कर सकते हैं । नीचे दिया गया क्वेरी उसी परिणाम को लौटाएगा जैसा प्रश्न में है, लेकिन यह सिर्फ 1 पंक्ति पाने के लक्ष्य के साथ नहीं बनाया जाएगा।

DECLARE @top INT = 1;

SELECT TOP (@top) dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
OPTION (OPTIMIZE FOR (@top = 987654321));

2

चूंकि आप एक कर रहे हैं TOP(1), मैं ORDER BYएक शुरुआत के लिए निर्धारक बनाने की सलाह देता हूं । बहुत कम से कम यह सुनिश्चित करेगा कि परिणाम कार्यात्मक रूप से अनुमानित हो (प्रतिगमन परीक्षण के लिए हमेशा उपयोगी)। ऐसा लगता है कि आपको इसके लिए DC.D_IDऔर जोड़ने की आवश्यकता CJ.CORRESPONDENCE_IDहै।

जब क्वेरी योजनाओं को देखते हैं, तो मुझे कभी-कभी यह क्वेरी को सरल बनाने के लिए निर्देशात्मक लगता है: संभवतः सभी संबंधित डीसी पंक्तियों को पहले से एक अस्थायी तालिका में चुन लें, ताकि कार्डिनिटी अनुमान के साथ मुद्दों को खत्म किया जा सके QUEUE_DATEऔर PRINT_LOCATION। यह तेजी से कम पंक्तिकोण दिया जाना चाहिए। यदि आप आवश्यक हो तो आप स्थायी तालिका में परिवर्तन किए बिना इस टेम्‍प टेबल में इंडेक्स जोड़ सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.