इस प्रश्न के सभी परिणामी कॉलमों का चयन करने की तुलना में मैं जिस कॉलम की परवाह करता हूं, उसका चयन तेजी से क्यों कर रहा है?


13

मेरे पास एक क्वेरी है जहां उपयोग करना select *न केवल बहुत कम पढ़ता है, बल्कि उपयोग करने की तुलना में काफी कम सीपीयू समय का उपयोग करता है select c.Foo

यह प्रश्न है:

select top 1000 c.ID
from ATable a
    join BTable b on b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
    join CTable c on c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
where (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff)
    and b.IsVoided = 0
    and c.ComplianceStatus in (3, 5)
    and c.ShipmentStatus in (1, 5, 6)
order by a.LastAnalyzedDate

यह 2,473,658 तार्किक रीड के साथ समाप्त हुआ, ज्यादातर टेबल बी में। इसमें 26,562 सीपीयू का इस्तेमाल किया गया और इसकी अवधि 7,965 थी।

यह उत्पन्न क्वेरी योजना है:

एकल स्तंभ के मान का चयन करने से योजना बनाएं PasteThePlan पर: https://www.brentozar.com/pastetheplan/?id=BJAp2PQQQ

जब मैं बदलने c.IDके लिए *, क्वेरी के साथ समाप्त 107,049 तार्किक पढ़ता है, काफी समान रूप से सभी तीन तालिकाओं के बीच फैल गया। इसमें 4,266 सीपीयू का इस्तेमाल किया गया था और इसकी अवधि 1,147 थी।

यह उत्पन्न क्वेरी योजना है:

सभी मानों के चयन से योजना PasteThePlan पर: https://www.brentozar.com/pastetheplan/?id=SyZYn7QUQ

मैंने जो ओबिश द्वारा सुझाए गए क्वेरी संकेत का उपयोग करने का प्रयास किया, इन परिणामों के साथ:
select c.IDबिना संकेत: https://www.brentozar.com/pastetheplan/?id=SJfBdOELm
select c.ID with hint: https://www.brentozar.com/pastetheplan/ ? आईडी = B1W ___ N87
select * बिना संकेत के: https://www.brentozar.com/pastetheplan/?id=HJ6qddEIm
select * with hint: https://www.brentozar.com/pastetheplan-?id=rJhudNIQ

OPTION(LOOP JOIN)संकेत का उपयोग करते हुए संकेत के select c.IDबिना संस्करण की तुलना में रीड्स की संख्या को काफी कम कर दिया है, लेकिन यह अभी भी लगभग 4x कर रहा है select *बिना किसी संकेत के क्वेरी को पढ़ता है। क्वेरी में जोड़ने OPTION(RECOMPILE, HASH JOIN)से select *यह मेरे द्वारा किए गए किसी भी प्रयास की तुलना में बहुत खराब प्रदर्शन करता है।

तालिकाओं और उनके अनुक्रमित का उपयोग करने के आंकड़ों को अपडेट करने के बाद WITH FULLSCAN, select c.IDक्वेरी बहुत तेजी से चल रही है:
select c.IDअपडेट से पहले: https://www.brentozar.com/pastetheplan/?id=SkiYoOEUm
select * अपडेट से पहले: https://www.brentozar.com/ pastetheplan /? id = ryrvodEUX
select c.ID अपडेट के बाद: https://www.brentozar.com/pastetheplan/?id=B1MRoO487
select * अपडेट के बाद: https://www.brentozar.com/pastetheplan/?id=Hk7si_V8m

select *अभी भी select c.IDकुल अवधि और कुल पढ़ता है ( select *लगभग आधा पढ़ता है) के संदर्भ में, लेकिन यह अधिक सीपीयू का उपयोग करता है। कुल मिलाकर वे अद्यतन से पहले की तुलना में बहुत करीब हैं, हालांकि योजनाएं अभी भी अलग हैं।

2016 में 2014 की संगतता मोड और 2014 में एक ही व्यवहार देखा गया है। दोनों योजनाओं के बीच असमानता को क्या समझा सकता है? क्या ऐसा हो सकता है कि "सही" इंडेक्स नहीं बनाए गए हैं? क्या आंकड़े थोड़ा आउट ऑफ डेट हो सकते हैं?

मैंने ONकई तरीकों से, शामिल होने के भाग तक विधेय को स्थानांतरित करने का प्रयास किया , लेकिन प्रत्येक बार क्वेरी योजना समान है।

इंडेक्स रीबिल्ड्स के बाद

मैंने क्वेरी में शामिल तीन तालिकाओं पर सभी अनुक्रमित को फिर से बनाया है। c.IDअभी भी सबसे अधिक पढ़ता है (दो बार से अधिक के रूप में *), लेकिन CPU उपयोग *संस्करण का लगभग आधा है । c.IDसंस्करण भी की छंटाई पर tempdb में गिरा ATable:
c.ID: https://www.brentozar.com/pastetheplan/?id=HyHIeDO87
* : https://www.brentozar.com/pastetheplan/?id=rJ4deDOIQ

मैंने इसे बिना समानता के संचालित करने के लिए मजबूर करने की कोशिश की, और इससे मुझे सबसे अच्छा प्रदर्शन करने वाला प्रश्न मिला: https://www.brentozar.com/pastetheplan/?id=SJn9-vuLX

मैं संचालकों की निष्पादन गणना पर ध्यान देता हूं कि बड़े सूचकांक की तलाश है जो केवल एकल-थ्रेडेड संस्करण में 1,000 बार निष्पादित की गई ऑर्डर कर रहा है, लेकिन विभिन्न ऑपरेटरों के 2,622 और 4,315 निष्पादन के बीच, समानांतर संस्करण में काफी अधिक है।

जवाबों:


4

यह सच है कि अधिक स्तंभों का चयन करने से तात्पर्य है कि क्वेरी के अनुरोधित परिणाम प्राप्त करने के लिए SQL सर्वर को अधिक मेहनत करनी पड़ सकती है। यदि क्वेरी ऑप्टिमाइज़र दोनों प्रश्नों के लिए सही क्वेरी योजना के साथ आने में सक्षम था, तो यह अपेक्षा करना उचित होगाSELECT *क्वेरी उस तालिका से लंबे समय तक चलने के लिए जो सभी तालिकाओं से सभी स्तंभों का चयन करती है। आपने अपनी जोड़ी के प्रश्नों के विपरीत देखा है। लागतों की तुलना करते समय आपको सावधान रहने की आवश्यकता है, लेकिन धीमी क्वेरी की कुल अनुमानित लागत 1090.08 अनुकूलक इकाइयों की है और तेज़ क्वेरी की कुल अनुमानित लागत 6823.11 ऑप्टिमाइज़र इकाइयों की है। इस मामले में, यह कहा जा सकता है कि ऑप्टिमाइज़र कुल क्वेरी लागतों के आकलन के साथ एक खराब काम करता है। इसने आपके SELECT * क्वेरी के लिए एक अलग योजना चुनी और उसे उम्मीद थी कि यह योजना अधिक महंगी होगी, लेकिन यहाँ ऐसा नहीं था। उस प्रकार का बेमेल कई कारणों से हो सकता है और सबसे सामान्य कारणों में से एक कार्डिनैलिटी अनुमान समस्याएं हैं। ऑपरेटर की लागत काफी हद तक कार्डिनैलिटी के अनुमानों से निर्धारित होती है। यदि किसी योजना के मुख्य बिंदु पर कार्डिनैलिटी का अनुमान गलत है, तो योजना की कुल लागत वास्तविकता को प्रतिबिंबित नहीं कर सकती है। यह एक सकल निरीक्षण है लेकिन मुझे आशा है कि यह समझने में मददगार होगा कि यहां क्या हो रहा है।

आइए चर्चा करते हुए शुरू करें कि SELECT *एक एकल कॉलम चुनने की तुलना में क्वेरी अधिक महंगी क्यों हो सकती है। SELECT *क्वेरी noncovering अनुक्रमित है, जो मतलब हो सकता है कि अनुकूलक इसके अलावा काम करने के लिए कॉलम यह जरूरत के सभी प्राप्त करने के लिए की जरूरत है या यह एक बड़ा सूचकांक से पढ़ने के लिए आवश्यकता हो सकती है में कुछ कवर अनुक्रमित बदल सकते हैं।SELECT *बड़े मध्यवर्ती परिणाम सेट भी हो सकते हैं जिन्हें क्वेरी निष्पादन के दौरान संसाधित करने की आवश्यकता होती है। आप इसे दोनों प्रश्नों में अनुमानित पंक्ति आकारों को देखकर कार्रवाई में देख सकते हैं। तेज़ क्वेरी में आपकी पंक्ति का आकार 664 बाइट से लेकर 3019 बाइट तक होता है। धीमी क्वेरी में आपकी पंक्ति का आकार 19 से 36 बाइट तक होता है। ऑपरेटर जैसे सॉर्ट या हैश बिल्ड को ब्लॉक करने पर बड़ी पंक्ति आकार के साथ डेटा के लिए उच्च लागत होगी क्योंकि SQL सर्वर जानता है कि डेटा की बड़ी मात्रा को सॉर्ट करना या हैश तालिका में बदलना अधिक महंगा है।

तेज क्वेरी को देखते हुए, ऑप्टिमाइज़र का अनुमान है कि उसे 2.4 मिलियन इंडेक्स का प्रयास करना है Database1.Schema1.Object5.Index3। यही वह जगह है जहां से अधिकांश योजना लागत आती है। फिर भी वास्तविक योजना से पता चलता है कि उस ऑपरेटर पर केवल 1332 सूचकांक खोजे गए थे। यदि आप वास्तविक तुलना उन लूप जॉइन के बाहरी हिस्सों के लिए अनुमानित पंक्तियों से करते हैं तो आपको बड़े अंतर दिखाई देंगे। ऑप्टिमाइज़र को लगता है कि क्वेरी के परिणामों के लिए आवश्यक पहली 1000 पंक्तियों को खोजने के लिए कई और अधिक सूचकांक की आवश्यकता होगी। इसलिए क्वेरी में अपेक्षाकृत उच्च लागत योजना है लेकिन इतनी जल्दी खत्म हो जाती है: जिस ऑपरेटर को सबसे महंगा होने का अनुमान लगाया गया था, वह अपने अपेक्षित कार्य का 0.1% से भी कम था।

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

आपको क्वेरी के दोनों संस्करणों को दूसरे संस्करण से जुड़े क्वेरी प्लान को जोड़ने के लिए क्वेरी संकेत जोड़ने में मदद मिल सकती है। क्वेरी संकेत यह पता लगाने के लिए एक अच्छा उपकरण हो सकता है कि अनुकूलक ने इसके कुछ विकल्प क्यों बनाए। यदि आप क्वेरी में जोड़ते OPTION (RECOMPILE, HASH JOIN)हैं, तो SELECT *मुझे उम्मीद है कि आपको हैश ज्वाइन क्वेरी के लिए एक समान क्वेरी योजना दिखाई देगी। मुझे यह भी उम्मीद है कि हैश जॉइन प्लान के लिए क्वेरी की लागत बहुत अधिक होगी क्योंकि आपकी पंक्ति का आकार बहुत बड़ा है। इसलिए हो सकता है कि क्वेरी के लिए हैश ज्वाइन क्वेरी क्यों नहीं चुनी गई SELECT *। यदि आप OPTION (LOOP JOIN)उस क्वेरी में जोड़ते हैं जो सिर्फ एक कॉलम का चयन करता है, तो मुझे उम्मीद है कि आप एक क्वेरी प्लान को उसी के समान देखेंगेSELECT *क्वेरी। इस स्थिति में, पंक्ति आकार को कम करने से समग्र क्वेरी लागत पर अधिक प्रभाव नहीं होना चाहिए। आप मुख्य लुकअप को छोड़ सकते हैं लेकिन यह अनुमानित लागत का एक छोटा प्रतिशत है।

सारांश में, मैं उम्मीद करता हूं कि SELECT *क्वेरी को संतुष्ट करने के लिए बड़े पंक्ति आकार को एक हैश जॉइन प्लान के बजाय एक लूप जॉइन प्लान की ओर ऑप्टिमाइज़र को पुश करना होगा। लूप जॉइन प्लान की लागत अधिक होती है, क्योंकि यह कार्डिनैलिटी अनुमान के मुद्दों के कारण होना चाहिए। सिर्फ एक कॉलम का चयन करके पंक्ति के आकार को कम करने से हैश ज्वाइन प्लान की लागत बहुत कम हो जाती है, लेकिन संभवतः लूप जॉइन प्लान की लागत पर अधिक प्रभाव नहीं पड़ेगा, इसलिए आप कम कुशल हैश जॉइन प्लान के साथ समाप्त होते हैं। अनाम योजना के लिए इससे अधिक कहना कठिन है।


आपके विस्तार और जानकारीपूर्ण उत्तर के लिए बहुत बहुत धन्यवाद। मैंने आपके द्वारा सुझाए गए संकेतों को जोड़ने की कोशिश की। इसने select c.IDक्वेरी को बहुत तेज़ कर दिया, लेकिन यह अभी भी कुछ अतिरिक्त काम कर रहा है select *, जो क्वेरी, बिना संकेत के करता है।
एल मिलर

2

बासी आंकड़े निश्चित रूप से ऑप्टिमाइज़र को डेटा खोजने की एक खराब विधि चुनने का कारण बन सकते हैं। क्या आपने सूचकांक पर UPDATE STATISTICS ... WITH FULLSCANपूर्ण करने या करने की कोशिश की है REBUILD? कोशिश करो और देखो कि यह मदद करता है।

अपडेट करें

ओपी के एक अपडेट के अनुसार:

तालिकाओं और उनके अनुक्रमित का उपयोग करने के आंकड़ों को अपडेट करने के बाद WITH FULLSCAN, select c.IDक्वेरी बहुत तेजी से चल रही है

इसलिए, अब, यदि केवल कार्रवाई की गई थी UPDATE STATISTICS, तो एक इंडेक्स REBUILD(नहीं REORGANIZE) करने की कोशिश करें क्योंकि मैंने देखा है कि अनुमानित पंक्ति गणनाओं के साथ मदद करें जहां दोनों UPDATE STATISTICSऔर इंडेक्स REORGANIZEनहीं किया था।


मैं सप्ताहांत पर पुनर्निर्माण के लिए शामिल तीन तालिकाओं पर सभी अनुक्रमित प्राप्त करने में सक्षम था, और उन परिणामों को प्रतिबिंबित करने के लिए अपनी पोस्ट को अपडेट किया है।
एल। मिलर

-1
  1. क्या आप कृपया सूचकांक स्क्रिप्ट शामिल कर सकते हैं?
  2. क्या आपने "पैरामीटर सूँघने" के साथ संभावित मुद्दों को समाप्त कर दिया है? https://www.mssqltips.com/sqlservertip/3257/different-approaches-to-correct-sql-server-parameter-sniffing/
  3. मैंने इस तकनीक को कुछ मामलों में मददगार पाया है:
    ए) इन नियमों का पालन करते हुए, उप-तालिका के रूप में प्रत्येक तालिका को फिर से लिखना:
    बी) का चयन करें - पहले कॉलम में शामिल हों
    ग) PREDICATES - अपने संबंधित उपखंडों में स्थानांतरित करें
    d) ORD BY - उनके द्वारा आगे बढ़ें संबंधित उपश्रेणियाँ, JOIN COLUMNS FIRST
    e पर छाँटें ) अपनी अंतिम छँटाई और चयन के लिए एक आवरण क्वेरी जोड़ें।

यह विचार है कि प्रत्येक सबसेलेक्ट के अंदर ज्वाइन कॉलम को प्री-सॉर्ट करना, प्रत्येक सेलेक्ट लिस्ट में पहले कॉलम को जोड़ना।

यही है जो मेरा मतलब है....

SELECT ... wrapper query
FROM
(
    SELECT ...
    FROM
        (SELECT ClientID, ShipKey, NextAnalysisDate
         FROM ATABLE
         WHERE (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff) -- Predicates
         ORDER BY OrderKey, ClientID, LastAnalyzedDate  ---- Pre-sort the join columns
        ) as a
        JOIN 
        (SELECT OrderKey, ClientID, OrderID, IsVoided
         FROM BTABLE
         WHERE IsVoided = 0             ---- Include all predicates
         ORDER BY OrderKey, OrderID, IsVoided       ---- Pre-sort the join columns
        ) as b ON b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
        JOIN
        (SELECT OrderID, ShipKey, ComplianceStatus, ShipmentStatus, ID
         FROM CTABLE
         WHERE ComplianceStatus in (3, 5)       ---- Include all predicates
             AND ShipmentStatus in (1, 5, 6)        ---- Include all predicates
         ORDER BY OrderID, ShipKey          ---- Pre-sort the join columns
        ) as c ON c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
) as d
ORDER BY d.LastAnalyzedDate

1
1. मैं मूल पोस्ट में इंडेक्स डीडीएल स्क्रिप्ट जोड़ने की कोशिश करूंगा, जो उन्हें "स्क्रब" करने में थोड़ा समय ले सकता है। 2. मैंने इस संभावना का परीक्षण चलने से पहले योजना कैश को साफ करने और वास्तविक मान के साथ बाइंड पैरामीटर को प्रतिस्थापित करके किया। 3. मैंने यह प्रयास किया, लेकिन ORDER BYएक TOP, FORXML, आदि के बिना एक उपश्रेणी में अमान्य है। मैंने इसे बिना ORDER BYखंडों के आजमाया लेकिन यह एक ही योजना थी।
एल मिलर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.