SQL सर्वर 2014 में क्वेरी 100x धीमा, पंक्ति गणना स्पूल पंक्ति अपराधी का अनुमान लगाती है?


13

मेरे पास एक क्वेरी है जो SQL Server 2012 में 800 मिलीसेकंड में चलती है और SQL सर्वर 2014 में लगभग 170 सेकंड लगती है । मुझे लगता है कि मैंने इसे Row Count Spoolऑपरेटर के लिए एक खराब कार्डिनैलिटी अनुमान से कम कर दिया है । मैंने स्पूल ऑपरेटरों (जैसे, यहाँ और यहाँ ) के बारे में थोड़ा पढ़ा है , लेकिन मुझे अभी भी कुछ चीजों को समझने में परेशानी हो रही है:

  • इस क्वेरी को Row Count Spoolऑपरेटर की आवश्यकता क्यों है ? मुझे नहीं लगता कि यह शुद्धता के लिए आवश्यक है, इसलिए यह किस विशिष्ट अनुकूलन को प्रदान करने की कोशिश कर रहा है?
  • SQL सर्वर का अनुमान क्यों है कि Row Count Spoolऑपरेटर से जुड़ने से सभी पंक्तियों को हटा दिया जाता है?
  • यह SQL Server 2014 में एक बग है? यदि हां, तो मैं कनेक्ट में फ़ाइल करूँगा। लेकिन मैं पहले एक गहरी समझ चाहता हूं।

नोट: मैं क्वेरी को फिर से लिख सकता हूं LEFT JOINया SQL Server 2012 और SQL Server 2014 दोनों में स्वीकार्य प्रदर्शन प्राप्त करने के लिए तालिकाओं में अनुक्रमणिका जोड़ सकता हूं । इसलिए यह प्रश्न इस विशिष्ट क्वेरी को समझने और योजना के बारे में अधिक गहराई से और कम के बारे में है। क्वेरी को अलग तरीके से कैसे वाक्यांशित करें।


धीमी क्वेरी

पूर्ण परीक्षण स्क्रिप्ट के लिए इस पास्टबिन को देखें । यहाँ मैं देख रहा हूँ विशिष्ट परीक्षण क्वेरी है:

-- Prune any existing customers from the set of potential new customers
-- This query is much slower than expected in SQL Server 2014 
SELECT *
FROM #potentialNewCustomers -- 10K rows
WHERE cust_nbr NOT IN (
    SELECT cust_nbr
    FROM #existingCustomers -- 1MM rows
)


SQL सर्वर 2014: अनुमानित क्वेरी योजना

एसक्यूएल सर्वर का मानना है कि Left Anti Semi Joinकरने के लिए Row Count Spool10,000 पंक्तियों 1 पंक्ति पर नीचे फिल्टर करेगा। इस कारण से, यह LOOP JOINबाद में शामिल होने के लिए चयन करता है #existingCustomers

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


SQL सर्वर 2014: वास्तविक क्वेरी योजना

जैसा कि अपेक्षित था (हर कोई लेकिन SQL सर्वर!), Row Count Spoolकिसी भी पंक्तियों को नहीं हटाता था। इसलिए हम 10,000 बार लूप कर रहे हैं जब SQL सर्वर सिर्फ एक बार लूप की उम्मीद करता है।

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


SQL सर्वर 2012: अनुमानित क्वेरी योजना

SQL सर्वर 2012 (या OPTION (QUERYTRACEON 9481)SQL सर्वर 2014) का उपयोग करते समय, Row Count Spoolपंक्तियों के अनुमानित # को कम नहीं करता है और एक हैश ज्वाइन चुना जाता है, जिसके परिणामस्वरूप एक बेहतर योजना बनती है।

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

बाईं ओर फिर से लिखें

संदर्भ के लिए, यहां एक तरीका है कि मैं सभी SQL सर्वर 2012, 2014 और 2016 में अच्छा प्रदर्शन प्राप्त करने के लिए क्वेरी को फिर से लिख सकता हूं। हालांकि, मैं अभी भी ऊपर दिए गए क्वेरी के विशिष्ट व्यवहार में रुचि रखता हूं और चाहे नए SQL Server 2014 कार्डिनैलिटी एस्टीमेटर में एक बग है।

-- Re-writing with LEFT JOIN yields much better performance in 2012/2014/2016
SELECT n.*
FROM #potentialNewCustomers n
LEFT JOIN (SELECT 1 AS test, cust_nbr FROM #existingCustomers) c
    ON c.cust_nbr = n.cust_nbr
WHERE c.test IS NULL

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

जवाबों:


10

इस क्वेरी को पंक्ति गणना स्पूल ऑपरेटर की आवश्यकता क्यों है? ... क्या विशिष्ट अनुकूलन प्रदान करने की कोशिश कर रहा है?

इसमें cust_nbrस्तंभ #existingCustomersअशक्त है। यदि इसमें वास्तव में कोई नल शामिल है, तो यहां सही प्रतिक्रिया शून्य पंक्तियों को वापस करना है ( NOT IN (NULL,...) हमेशा एक खाली परिणाम सेट करेगा।)।

तो क्वेरी के रूप में सोचा जा सकता है

SELECT p.*
FROM   #potentialNewCustomers p
WHERE  NOT EXISTS (SELECT *
                   FROM   #existingCustomers e1
                   WHERE  p.cust_nbr = e1.cust_nbr)
       AND NOT EXISTS (SELECT *
                       FROM   #existingCustomers e2
                       WHERE  e2.cust_nbr IS NULL) 

मूल्यांकन करने के लिए होने से बचने के लिए वहाँ पंक्तिबद्ध स्पूल के साथ

EXISTS (SELECT *
        FROM   #existingCustomers e2
        WHERE  e2.cust_nbr IS NULL) 

एक से ज्यादा बार।

यह सिर्फ एक ऐसा मामला प्रतीत होता है जहां मान्यताओं में एक छोटा सा अंतर प्रदर्शन में काफी विनाशकारी अंतर ला सकता है।

नीचे के रूप में एक पंक्ति को अद्यतन करने के बाद ...

UPDATE #existingCustomers
SET    cust_nbr = NULL
WHERE  cust_nbr = 1;

... क्वेरी एक सेकंड से भी कम समय में पूरी हुई। योजना के वास्तविक और अनुमानित संस्करणों में पंक्ति अब मायने रखती है।

SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT *
FROM   #potentialNewCustomers
WHERE  cust_nbr NOT IN (SELECT cust_nbr
                        FROM   #existingCustomers 
                       ) 

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

ऊपर वर्णित के रूप में शून्य पंक्तियाँ आउटपुट हैं।

SQL सर्वर में सांख्यिकी हिस्टोग्राम और ऑटो अपडेट थ्रेसहोल्ड इस तरह के एकल पंक्ति परिवर्तन का पता लगाने के लिए पर्याप्त रूप से दानेदार नहीं हैं। यकीनन अगर स्तंभ अशक्त है, तो इस आधार पर काम करना उचित हो सकता है कि इसमें कम से कम एक हो, NULLभले ही आँकड़े हिस्टोग्राम वर्तमान में यह संकेत न दें कि कोई भी है।


9

इस क्वेरी को पंक्ति गणना स्पूल ऑपरेटर की आवश्यकता क्यों है? मुझे नहीं लगता कि यह शुद्धता के लिए आवश्यक है, इसलिए यह किस विशिष्ट अनुकूलन को प्रदान करने की कोशिश कर रहा है?

इस प्रश्न के लिए मार्टिन का पूरा उत्तर देखें । प्रमुख मुद्दा यह है कि अगर भीतर एक ही पंक्ति है NOT INहै NULL, बूलियन तर्क बाहर काम करता है जैसे कि "सही जवाब शून्य पंक्तियों को वापस करने के लिए है।" Row Count Spoolऑपरेटर इस (आवश्यक) तर्क के अनुकूलन है।

SQL सर्वर का अनुमान क्यों है कि पंक्ति गणना स्पूल ऑपरेटर में शामिल होने से सभी पंक्तियों को हटा दिया जाता है?

Microsoft SQL 2014 कार्डिनैलिटी एस्टीमेटर पर एक उत्कृष्ट श्वेत पत्र प्रदान करता है । इस दस्तावेज़ में, मुझे निम्नलिखित जानकारी मिली:

नए CE मान लेता है कि डेटा में हिस्टोग्राम की सीमा से बाहर होने पर भी क्वाइयर वैल्यूज डेटासेट में मौजूद होता है। इस उदाहरण में नया CE एक औसत आवृत्ति का उपयोग करता है जिसकी गणना घनत्व द्वारा तालिका कार्डिनैलिटी को गुणा करके की जाती है।

अक्सर, इस तरह का बदलाव बहुत अच्छा होता है; यह आरोही प्रमुख समस्या को कम करता है और आम तौर पर आँकड़ों के आधार पर आउट-ऑफ-रेंज के मान के लिए अधिक रूढ़िवादी क्वेरी योजना (उच्च पंक्ति अनुमान) प्राप्त करता है।

हालांकि, इस विशिष्ट मामले में, यह मान लेना कि NULLमूल्य मिल जाएगा, इस धारणा की ओर जाता है कि Row Count Spoolविल में शामिल होने से सभी पंक्तियों को फ़िल्टर किया जाएगा #potentialNewCustomers। इस मामले में जहां वास्तव में एक NULLपंक्ति है, यह एक सही अनुमान है (जैसा कि मार्टिन के उत्तर में देखा गया है)। हालाँकि, उस स्थिति में जहां एक NULLपंक्ति नहीं होती है, प्रभाव विनाशकारी हो सकता है क्योंकि SQL सर्वर 1 पंक्ति के बाद के जुड़ने वाले अनुमान का उत्पादन करता है, भले ही कितनी इनपुट पंक्तियाँ दिखाई दें। इससे क्वेरी प्लान के शेष भाग में बहुत ही खराब विकल्प शामिल हो सकते हैं।

क्या यह SQL 2014 में बग है? यदि हां, तो मैं कनेक्ट में फ़ाइल करूँगा। लेकिन मैं पहले एक गहरी समझ चाहता हूं।

मुझे लगता है कि यह बग के बीच ग्रे क्षेत्र में है और SQL सर्वर के नए कार्डिनैलिटी एस्टीमेटर के प्रदर्शन-प्रभाव की धारणा या सीमा है। हालाँकि, यह क्वर्क SQL 2012 के सापेक्ष प्रदर्शन में पर्याप्त अवरोधन पैदा कर सकता है जो एक अशक्त NOT INखंड के विशिष्ट मामले में होता है, जिसमें कोई NULLमान नहीं होता है ।

इसलिए, मैंने एक कनेक्ट मुद्दा दायर किया है ताकि SQL टीम कार्डिनैलिटी एस्टीमेटर को इस बदलाव के संभावित निहितार्थों के बारे में पता हो।

अपडेट: हम SQL16 के लिए अब CTP3 पर हैं, और मैंने पुष्टि की कि समस्या वहाँ नहीं होती है।


5

मार्टिन स्मिथ के जवाब और आपके आत्म-उत्तर ने सभी मुख्य बिंदुओं को सही तरीके से संबोधित किया है, मैं सिर्फ भविष्य के पाठकों के लिए एक क्षेत्र पर जोर देना चाहता हूं:

तो यह प्रश्न इस विशिष्ट क्वेरी को समझने के बारे में अधिक है और योजना के बारे में गहराई से और क्वेरी को अलग ढंग से कैसे लिखें, इसके बारे में कम है।

क्वेरी का घोषित उद्देश्य है:

-- Prune any existing customers from the set of potential new customers

यह आवश्यकता कई मायनों में SQL में व्यक्त करने के लिए आसान है। जो एक चुना जाता है वह शैली के रूप में ज्यादा कुछ भी होता है, लेकिन सभी मामलों में सही परिणाम वापस करने के लिए क्वेरी विनिर्देश लिखा जाना चाहिए। इसमें नल के लिए लेखांकन शामिल है।

तार्किक आवश्यकता को पूरी तरह से व्यक्त करना:

  • संभावित ग्राहक लौटाएँ जो पहले से ग्राहक नहीं हैं
  • प्रत्येक संभावित ग्राहक को एक बार में सूचीबद्ध करें
  • शून्य संभावित और मौजूदा ग्राहकों को छोड़ दें (जो भी शून्य ग्राहक का मतलब है)

फिर हम जो भी वाक्यविन्यास पसंद करते हैं उसका उपयोग करके उन आवश्यकताओं से मेल खाने वाली एक क्वेरी लिख सकते हैं। उदाहरण के लिए:

WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE
    DPNNC.cust_nbr NOT IN
    (
        SELECT 
            EC.cust_nbr 
        FROM #existingCustomers AS EC 
        WHERE 
            EC.cust_nbr IS NOT NULL
    );

यह एक कुशल निष्पादन योजना तैयार करता है, जो सही परिणाम देता है:

निष्पादन योजना

हम योजना या परिणामों को प्रभावित किए बिना या के NOT INरूप में व्यक्त कर सकते हैं :<> ALLNOT = ANY

WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE
    DPNNC.cust_nbr <> ALL
    (
        SELECT 
            EC.cust_nbr 
        FROM #existingCustomers AS EC 
        WHERE 
            EC.cust_nbr IS NOT NULL
    );
WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE
    NOT DPNNC.cust_nbr = ANY
    (
        SELECT 
            EC.cust_nbr 
        FROM #existingCustomers AS EC 
        WHERE 
            EC.cust_nbr IS NOT NULL
    );

या उपयोग कर रहा है NOT EXISTS:

WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE 
    NOT EXISTS
    (
        SELECT * 
        FROM #existingCustomers AS EC
        WHERE
            EC.cust_nbr = DPNNC.cust_nbr
            AND EC.cust_nbr IS NOT NULL
    );

कुछ भी नहीं जादू, इस बारे में नहीं है या कुछ भी विशेष रूप से उपयोग के बारे में आपत्तिजनक IN, ANYया ALL- हम सिर्फ क्वेरी सही ढंग से लिखने के लिए, तो यह हमेशा सही परिणाम देगा की जरूरत है।

सबसे कॉम्पैक्ट रूप का उपयोग करता है EXCEPT:

SELECT 
    PNC.cust_nbr 
FROM #potentialNewCustomers AS PNC
WHERE 
    PNC.cust_nbr IS NOT NULL
EXCEPT
SELECT
    EC.cust_nbr 
FROM #existingCustomers AS EC
WHERE 
    EC.cust_nbr IS NOT NULL;

यह सही परिणाम भी देता है, हालांकि बिटमैप फ़िल्टरिंग की अनुपस्थिति के कारण निष्पादन योजना कम कुशल हो सकती है:

गैर-बिटमैप निष्पादन योजना

मूल प्रश्न दिलचस्प है क्योंकि यह आवश्यक अशक्त जांच कार्यान्वयन के साथ एक प्रदर्शन-प्रभावित समस्या को उजागर करता है। इस उत्तर की बात यह है कि प्रश्न को सही ढंग से लिखने से समस्या से भी बचा जा सकता है।

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