SQL सर्वर 2014: असंगत स्वयं के लिए कोई स्पष्टीकरण कार्डिनैलिटी अनुमान में शामिल होता है?


27

SQL Server 2014 में निम्नलिखित क्वेरी योजना पर विचार करें:

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

क्वेरी प्लान में, एक सेल्फ-जॉइन ar.fId = ar.fId1 पंक्ति का एक अनुमान देता है। हालाँकि, यह एक तार्किक रूप से असंगत अनुमान है: arइसमें 20,608पंक्तियाँ और सिर्फ एक अलग मूल्य है fId(आंकड़ों में सटीक रूप से परिलक्षित)। इसलिए, यह जुड़ाव पंक्तियों ( ~424MMपंक्तियों) के पूर्ण क्रॉस उत्पाद का उत्पादन करता है , जिससे क्वेरी कई घंटों तक चलती है।

मुझे यह समझने में कठिन समय हो रहा है कि एसक्यूएल सर्वर एक अनुमान के साथ क्यों आएगा जो आंकड़ों के साथ असंगत साबित हो सकता है। कोई विचार?

प्रारंभिक जांच और अतिरिक्त विवरण

यहाँ पॉल के उत्तर के आधार पर , ऐसा लगता है कि एसक्यूएल 2012 और एसक्यूएल 2014 दोनों में शामिल होने का अनुमान लगाने के लिए कार्डिनैलिटी को आसानी से ऐसी स्थिति को संभालना चाहिए जहां दो समान हिस्टोग्राम की तुलना करने की आवश्यकता होती है।

मैंने ट्रेस फ़्लैग 2363 से आउटपुट के साथ शुरुआत की, लेकिन आसानी से समझ नहीं पा रहा था। निम्नलिखित मतलब झलकी कि SQL सर्वर के लिए हिस्टोग्राम तुलना कर रहा है fIdऔर bIdआदेश चयनात्मकता अनुमान लगाने के लिए की एक कि का उपयोग करता है केवल में शामिल होने fId? यदि हां, तो यह स्पष्ट रूप से सही नहीं होगा। या क्या मैं ट्रेस फ्लैग आउटपुट को गलत बता रहा हूं?

Plan for computation:
  CSelCalcExpressionComparedToExpression( QCOL: [ar].fId x_cmpEq QCOL: [ar].fId )
Loaded histogram for column QCOL: [ar].bId from stats with id 3
Loaded histogram for column QCOL: [ar].fId from stats with id 1
Selectivity: 0

ध्यान दें कि मैं कई वर्कअराउंड के साथ आया हूं, जो पूर्ण रीप्रो स्क्रिप्ट में शामिल हैं और इस क्वेरी को मिलीसेकंड तक नीचे लाते हैं। यह प्रश्न व्यवहार को समझने, भविष्य के प्रश्नों में इसे कैसे टालना है, और यह निर्धारित करने के लिए केंद्रित है कि क्या यह बग है जिसे Microsoft के साथ दायर किया जाना चाहिए।

यहाँ एक पूर्ण रीप्रो स्क्रिप्ट है , यहाँ ट्रेस ध्वज 2363 से पूर्ण आउटपुट है , और यहाँ क्वेरी और तालिका परिभाषाएँ हैं जो आप पूरी स्क्रिप्ट को खोले बिना जल्दी से देखना चाहते हैं:

WITH cte AS (
    SELECT ar.fId, 
        ar.bId,
        MIN(CONVERT(INT, ar.isT)) AS isT,
        MAX(CONVERT(INT, tcr.isS)) AS isS
    FROM  #SQL2014MinMaxAggregateCardinalityBug_ar ar 
    LEFT OUTER JOIN #SQL2014MinMaxAggregateCardinalityBug_tcr tcr
        ON tcr.rId = 508
        AND tcr.fId = ar.fId
        AND tcr.bId = ar.bId
    GROUP BY ar.fId, ar.bId
)
SELECT s.fId, s.bId, s.isS, t.isS
FROM cte s 
JOIN cte t 
    ON t.fId = s.fId 
    AND t.isT = 1

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_ar (
    fId INT NOT NULL,
    bId INT NOT NULL,
    isT BIT NOT NULL
    PRIMARY KEY (fId, bId)
)

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_tcr (
    rId INT NOT NULL,
    fId INT NOT NULL,
    bId INT NOT NULL,
    isS BIT NOT NULL
    PRIMARY KEY (rId, fId, bId, isS)
)

जवाबों:


23

मुझे यह समझने में कठिन समय हो रहा है कि एसक्यूएल सर्वर एक अनुमान के साथ क्यों आएगा जो आंकड़ों के साथ असंगत साबित हो सकता है।

संगति

स्थिरता की कोई सामान्य गारंटी नहीं है। अनुमान विभिन्न (लेकिन तार्किक रूप से समतुल्य) अलग-अलग समय पर अलग-अलग सांख्यिकीय विधियों का उपयोग करके गणना की जा सकती है।

तर्क के साथ कुछ भी गलत नहीं है जो कहता है कि एक समान उत्पाद का उत्पादन करने के लिए उन दो समान उपप्रकारों में शामिल होना चाहिए, लेकिन यह कहने के लिए समान रूप से कुछ भी नहीं है कि तर्क का विकल्प किसी भी अन्य की तुलना में अधिक ध्वनि है।

प्रारंभिक अनुमान

आपके विशिष्ट मामले में, जुड़ने के लिए प्रारंभिक कार्डिनलिटी अनुमान दो समान उपप्रकारों पर नहीं किया जाता है । उस समय पेड़ का आकार होता है:

  LogOp_Join
     LogOp_GbAgg
        LogOp_LeftOuterJoin
           LogOp_Get TBL: ar
           LogOp_Select
              LogOp_Get TBL: tcr
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [tcr] .rId
                 ScaOp_Const मान = 508
           ScaOp_Logical x_lopAnd
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .fId
                 ScaOp_Identifier [tcr] .fId
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .bId
                 ScaOp_Identifier [tcr] .bId
        AncOp_PrjList 
           AncOp_PrjEl Expr1003 
              ScaOp_AggFunc stopMax
                 ScaOp_Convert int
                    ScaOp_Identifier [tcr] .isS
     LogOp_Select
        LogOp_GbAgg
           LogOp_LeftOuterJoin
              LogOp_Get TBL: ar
              LogOp_Select
                 LogOp_Get TBL: tcr
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [tcr] .rId
                    ScaOp_Const मान = 508
              ScaOp_Logical x_lopAnd
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .fId
                    ScaOp_Identifier [tcr] .fId
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .bId
                    ScaOp_Identifier [tcr] .bId
           AncOp_PrjList 
              AncOp_PrjEl Expr1006 
                 ScaOp_AggFunc रोकें
                    ScaOp_Convert int
                       ScaOp_Identifier [ar] .isT
              AncOp_PrjEl Expr1007 
                 ScaOp_AggFunc stopMax
                    ScaOp_Convert int
                       ScaOp_Identifier [tcr] .isS
        ScaOp_Comp x_cmpEq
           ScaOp_Identifier Expr1006 
           ScaOp_Const मान = 1
     ScaOp_Comp x_cmpEq
        ScaOp_Identifier QCOL: [ar] .fId
        ScaOp_Identifier QCOL: [ar] .fId

पहले ज्वाइन इनपुट में एक अनप्रोजेक्ट एग्रीगेट सरलीकृत किया गया है, और दूसरा ज्वाइन इनपुट उसके t.isT = 1नीचे प्रेडिकेटेट है, जहां t.isTहै MIN(CONVERT(INT, ar.isT))। इसके बावजूद, isTविधेय के लिए चयनात्मकता CSelCalcColumnInIntervalएक हिस्टोग्राम पर उपयोग करने में सक्षम है :

  CSelCalcColumnInInterval
      कॉलम: COL: Expr1006 

कॉलम QCOL के लिए लोड किया गया हिस्टोग्राम: [३] आईडी ३ के साथ आँकड़ों से

चयनात्मकता: 4.85248e-005

आँकड़े संग्रह उत्पन्न: 
  CStCollFilter (ID = 11, CARD = 1)
      CStCollGroupBy (आईडी = 10, कार्ड = 20608)
          CStCollOuterJoin (ID = 9, CARD = 20608 x_jtLeftOuter)
              CStCollBaseTable (ID = 3, CARD = 20608 TBL: ar)
              CStCollFilter (ID = 8, CARD = 1)
                  CStCollBaseTable (ID = 4, CARD = 28 TBL: tcr)

(सही) उम्मीद इस विधेय द्वारा 20,608 पंक्तियों को 1 पंक्ति तक कम करने के लिए है।

अनुमान में शामिल हों

अब सवाल यह है कि कैसे दूसरे जॉइन इनपुट से 20,608 पंक्तियाँ इस एक पंक्ति के साथ मेल खाएँगी:

  LogOp_Join
      CStCollGroupBy (ID = 7, CARD = 20608)
          CStCollOuterJoin (ID = 6, CARD = 20608 x_jtLeftOuter)
              ...

      CStCollFilter (ID = 11, CARD = 1)
          CStCollGroupBy (आईडी = 10, कार्ड = 20608)
              ...

      ScaOp_Comp x_cmpEq
          ScaOp_Identifier QCOL: [ar] .fId
          ScaOp_Identifier QCOL: [ar] .fId

सामान्य रूप से जुड़ने का अनुमान लगाने के कई अलग-अलग तरीके हैं। हम कर सकते हैं, उदाहरण के लिए:

  • प्रत्येक उपप्रकार में प्रत्येक योजना ऑपरेटर पर नए हिस्टोग्राम्स को सम्मिलित करें, उन्हें शामिल होने (आवश्यक रूप से चरण मानों को प्रक्षेपित) पर संरेखित करें, और देखें कि वे कैसे मेल खाते हैं; या
  • हिस्टोग्राम्स का एक सरल 'मोटे' संरेखण करें (न्यूनतम और अधिकतम मूल्यों का उपयोग करते हुए, चरण-दर-चरण नहीं); या
  • अकेले शामिल होने वाले स्तंभों के लिए अलग-अलग चयन की गणना करें (आधार तालिका से, और बिना किसी फ़िल्टरिंग के), फिर गैर-सम्मिलित विधेयकों (ओं) की चयनात्मकता प्रभाव में जोड़ें।
  • ...

उपयोग में कार्डिनैलिटी अनुमानक, और कुछ अनुमानों के आधार पर, उनमें से किसी (या भिन्नता) का उपयोग किया जा सकता है। अधिक के लिए Microsoft व्हाइट पेपर SQL सर्वर 2014 कार्डिनैलिटी एस्टीमेटर के साथ अपनी क्वेरी योजनाओं का अनुकूलन देखें ।

बग?

अब, जैसा कि प्रश्न में कहा गया है, इस मामले में 'सरल' सिंगल-कॉलम जॉइन (ऑन fId) CSelCalcExpressionComparedToExpressionकैलकुलेटर का उपयोग करता है :

गणना के लिए योजना:

  CSelCalcExpressionComparedToExpression [ar] .fId x_cmpE [[] .fId

कॉलम QCOL के लिए लोड किया गया हिस्टोग्राम: [ar] .b 2 आईडी वाले आंकड़ों से
कॉलम QCOL के लिए लोड किए गए हिस्टोग्राम: आईडी 1 के साथ आँकड़ों से .fId

चयनात्मकता: 0

यह गणना मानती है कि 1 फ़िल्टर्ड पंक्ति के साथ 20,608 पंक्तियों में शामिल होने पर एक शून्य चयनात्मकता होगी: कोई पंक्तियाँ मेल नहीं खाएंगी (अंतिम योजनाओं में एक पंक्ति के रूप में रिपोर्ट की गई)। क्या यह गलत है? हाँ, शायद यहाँ नए CE में एक बग है। कोई यह तर्क दे सकता है कि 1 पंक्ति सभी पंक्तियों या किसी से मेल नहीं खाएगी, इसलिए परिणाम उचित हो सकता है, लेकिन अन्यथा विश्वास करने का कारण है।

विवरण वास्तव में मुश्किल हैं, लेकिन फ़िल्टर fIdकी चयनात्मकता द्वारा संशोधित अनफ़िल्टर्ड हिस्टोग्राम्स के आधार पर अनुमान की अपेक्षा , 20608 * 20608 * 4.85248e-005 = 20608पंक्तियों को देना बहुत ही उचित है।

इस गणना के बाद कैलकुलेटर का उपयोग कर का मतलब होगा CSelCalcSimpleJoinWithDistinctCountsकी बजाय CSelCalcExpressionComparedToExpression। ऐसा करने के लिए कोई प्रलेखित तरीका नहीं है, लेकिन यदि आप उत्सुक हैं, तो आप अनअस्पोज़ेड ट्रेस ध्वज 9479 को सक्षम कर सकते हैं:

9479 की योजना

नोट अंतिम जुड़ाव दो एकल-पंक्ति इनपुट से 20,608 पंक्तियों का उत्पादन करता है, लेकिन यह एक आश्चर्य नहीं होना चाहिए। यह TF 9481 के तहत मूल CE द्वारा निर्मित एक ही योजना है।

मैंने उल्लेख किया कि विवरण मुश्किल (और समय लेने वाली जांच करने के लिए) हैं, लेकिन जहां तक ​​मैं बता सकता हूं, समस्या का मूल कारण विधेय से संबंधित है rId = 508, एक शून्य चयनात्मकता के साथ। यह शून्य अनुमान सामान्य तरीके से एक पंक्ति में उठाया जाता है, जो उस प्रश्न में शामिल होने पर शून्य चयनात्मकता अनुमान में योगदान करने के लिए प्रकट होता है जब यह इनपुट ट्री में कम विधेयकों के लिए खाता है (इसलिए लोडिंग आँकड़े bId)।

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

9473 की योजना

CSelCalcExpressionComparedToExpressionसाथ कार्डिनैलिटी अनुमान के व्यवहार को भी एक अन्य अनिर्दिष्ट रूपांतर झंडे (9494) के साथ `` bId 'के लिए खाता नहीं होने के लिए संशोधित किया जा सकता है। मैं इन सभी का उल्लेख करता हूं क्योंकि मुझे पता है कि आपको ऐसी चीजों में रुचि है; इसलिए नहीं कि वे एक समाधान प्रस्तुत करते हैं। जब तक आप Microsoft को समस्या की रिपोर्ट करते हैं, और वे इसे संबोधित करते हैं (या नहीं), क्वेरी को अलग तरीके से व्यक्त करना संभवतः आगे बढ़ने का सबसे अच्छा तरीका है। भले ही व्यवहार जानबूझकर हो या न हो, उन्हें प्रतिगमन के बारे में सुनने के लिए दिलचस्पी होनी चाहिए।

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

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