2014 में वापस, मैंने प्रदर्शन ट्यूनिंग द होल क्वेरी प्लान नामक एक लेख लिखा था। इसने मध्यम रूप से बड़े डेटासेट से अपेक्षाकृत कम संख्या में अलग-अलग मूल्यों को खोजने के तरीकों को देखा, और निष्कर्ष निकाला कि एक पुनरावर्ती समाधान इष्टतम हो सकता है। यह अनुवर्ती पोस्ट बड़ी संख्या में पंक्तियों का उपयोग करते हुए SQL सर्वर 2019 के प्रश्न पर फिर से विचार करती है।
टेस्ट पर्यावरण
मैं 50 जीबी स्टैक ओवरफ्लो 2013 डेटाबेस का उपयोग करूंगा, लेकिन कम संख्या में विशिष्ट मूल्यों वाली कोई भी बड़ी तालिका काम करेगी।
मैं BountyAmount
. में अलग-अलग मानों की तलाश में रहूंगा dbo.Votes
. का कॉलम तालिका, इनाम राशि के आरोही क्रम में प्रस्तुत की गई है। वोट तालिका में केवल 53 मिलियन पंक्तियां हैं (52,928,720 सटीक होने के लिए)। केवल 19 अलग-अलग इनाम राशियाँ हैं, जिनमें NULL
. शामिल हैं ।
स्टैक ओवरफ्लो 2013 डेटाबेस डाउनलोड समय को कम करने के लिए गैर-संकुल अनुक्रमणिका के बिना आता है। Id
. पर एक संकुल प्राथमिक कुंजी अनुक्रमणिका है dbo.Votes
. का कॉलम टेबल। यह SQL Server 2008 संगतता (स्तर 100) पर सेट है, लेकिन हम SQL Server 2017 (स्तर 140) की अधिक आधुनिक सेटिंग के साथ प्रारंभ करेंगे:
ALTER DATABASE StackOverflow2013
SET COMPATIBILITY_LEVEL = 140;
SQL सर्वर 2019 CU 2 का उपयोग करके मेरे लैपटॉप पर परीक्षण किए गए थे। इस मशीन में 2.4GHz की बेस स्पीड के साथ चार i7 CPU (हाइपरथ्रेडेड 8) हैं। इसमें 32GB रैम है, 24GB SQL सर्वर 2019 इंस्टेंस के लिए उपलब्ध है। समांतरता के लिए लागत सीमा 50 पर सेट है।
प्रत्येक परीक्षा परिणाम स्मृति में सभी आवश्यक डेटा और अनुक्रमणिका पृष्ठों के साथ सर्वश्रेष्ठ दस रनों का प्रतिनिधित्व करता है।
1. रो स्टोर क्लस्टर्ड इंडेक्स
बेसलाइन सेट करने के लिए, पहला रन बिना किसी नई अनुक्रमण के एक सीरियल क्वेरी है (और याद रखें कि यह संगतता स्तर 140 पर सेट डेटाबेस के साथ है):
dbo से अलग V.इनाम राशि चुनेंयह क्लस्टर इंडेक्स को स्कैन करता है और
BountyAmount
के अलग-अलग मानों को खोजने के लिए रो-मोड हैश एग्रीगेट का उपयोग करता है। :सीरियल क्लस्टर इंडेक्स प्लान
इसमें 10,500ms लगते हैं CPU समय की समान मात्रा का उपयोग करके पूरा करने के लिए। याद रखें कि स्मृति में सभी डेटा के साथ दस रन से अधिक का यह सबसे अच्छा समय है।
BountyAmount
. पर स्वचालित रूप से बनाए गए नमूना आंकड़े कॉलम पहले रन पर बनाए गए थे।बीता हुआ समय का लगभग आधा क्लस्टर्ड इंडेक्स स्कैन पर और लगभग आधा हैश मैच एग्रीगेट पर खर्च होता है। सॉर्ट करने के लिए केवल 19 पंक्तियाँ हैं, इसलिए यह केवल 1ms या उससे अधिक की खपत करता है। इस योजना के सभी ऑपरेटर पंक्ति मोड निष्पादन का उपयोग करते हैं।
MAXDOP 1
को हटाया जा रहा है संकेत एक समानांतर योजना देता है:समानांतर क्लस्टर इंडेक्स योजना
यह वह योजना है जिसे ऑप्टिमाइज़र मेरे कॉन्फ़िगरेशन में बिना किसी संकेत के चुनता है। यह 4,200ms . में परिणाम देता है कुल 32,800ms CPU (DOP 8 पर) का उपयोग कर रहा है।
2. गैर-संकुल सूचकांक
केवल
BountyAmount
. खोजने के लिए पूरी तालिका को स्कैन करना अक्षम लगता है, इसलिए इस क्वेरी के लिए आवश्यक केवल एक कॉलम पर एक गैर-संकुल अनुक्रमणिका जोड़ने का प्रयास करना स्वाभाविक है:dbo.Votes (BountyAmount) पर नॉनक्लस्टर्ड इंडेक्स b बनाएं;इस इंडेक्स को बनाने में थोड़ा समय लगता है (1m 40s)।
MAXDOP 1
क्वेरी अब एक स्ट्रीम एग्रीगेट का उपयोग करती है क्योंकि अनुकूलकBountyAmount
में पंक्तियों को प्रस्तुत करने के लिए गैर-संकुल अनुक्रमणिका का उपयोग कर सकता है आदेश:सीरियल नॉनक्लस्टर रो मोड प्लान
यह 9,300ms . तक चलता है (सीपीयू समय की समान मात्रा का उपभोग)। मूल 10,500ms पर एक उपयोगी सुधार लेकिन शायद ही पृथ्वी-बिखरने वाला।
MAXDOP 1
को हटाया जा रहा है संकेत स्थानीय (प्रति-थ्रेड) एकत्रीकरण के साथ समानांतर योजना देता है:समानांतर गैर-संकुल पंक्ति मोड योजना
यह 3,400ms . में निष्पादित होता है 25,800ms CPU समय का उपयोग करना। हम नई अनुक्रमणिका पर पंक्ति या पृष्ठ संपीड़न के साथ बेहतर करने में सक्षम हो सकते हैं, लेकिन मैं और अधिक दिलचस्प विकल्पों पर आगे बढ़ना चाहता हूं।
3. रो स्टोर पर बैच मोड (BMOR)
अब डेटाबेस को SQL सर्वर 2019 संगतता मोड का उपयोग करके सेट करें:
ALTER DATABASE StackOverflow2013SET COMPATIBILITY_LEVEL =150;यह ऑप्टिमाइज़र को पंक्ति स्टोर पर बैच मोड चुनने की स्वतंत्रता देता है यदि वह इसे सार्थक मानता है। यह कॉलम स्टोर इंडेक्स की आवश्यकता के बिना बैच मोड निष्पादन के कुछ लाभ प्रदान कर सकता है। गहरे तकनीकी विवरण और अनिर्दिष्ट विकल्पों के लिए, इस विषय पर दिमित्री पिलुगिन का उत्कृष्ट लेख देखें।
दुर्भाग्य से, ऑप्टिमाइज़र अभी भी धारावाहिक और समानांतर परीक्षण प्रश्नों दोनों के लिए स्ट्रीम एग्रीगेट का उपयोग करके पूरी तरह से पंक्ति मोड निष्पादन का चयन करता है। पंक्ति स्टोर निष्पादन योजना पर बैच मोड प्राप्त करने के लिए, हम हैश मैच (जो बैच मोड में चल सकते हैं) का उपयोग करके एकत्रीकरण को प्रोत्साहित करने के लिए एक संकेत जोड़ सकते हैं:
dbo से DISTINCT V.BountyAmount चुनें। V.BountyAmountOPTION (HASH GROUP, MAXDOP 1) के अनुसार VORDER के रूप में वोट;यह हमें बैच मोड में चलने वाले सभी ऑपरेटरों के साथ एक योजना देता है:
रो स्टोर प्लान पर सीरियल बैच मोड
परिणाम 2,600ms . में लौटाए जाते हैं (सभी सीपीयू हमेशा की तरह)। यह पहले से ही समानांतर . से तेज है बहुत कम CPU (2,600ms बनाम 25,800ms) का उपयोग करते हुए पंक्ति मोड योजना (3,400ms बीत गई)।
MAXDOP 1
को हटाया जा रहा है संकेत (लेकिनHASH GROUP
रखते हुए) ) रो स्टोर प्लान पर समानांतर बैच मोड देता है:पंक्ति स्टोर योजना पर समानांतर बैच मोड
यह केवल 725ms . में चलता है 5,700ms CPU का उपयोग कर रहा है।
4. कॉलम स्टोर पर बैच मोड
पंक्ति स्टोर परिणाम पर समानांतर बैच मोड एक प्रभावशाली सुधार है। हम डेटा का एक कॉलम स्टोर प्रतिनिधित्व प्रदान करके और भी बेहतर कर सकते हैं। बाकी सब कुछ वैसा ही रखने के लिए, मैं एक गैर-संकुल जोड़ूंगा हमें जिस कॉलम की जरूरत है उस पर कॉलम स्टोर इंडेक्स:
dbo.Votes (BountyAmount) पर गैर-क्लस्टर्ड कॉलमस्टोर इंडेक्स बनाएं;यह मौजूदा बी-पेड़ गैर-संकुल अनुक्रमणिका से भरा हुआ है और इसे बनाने में केवल 15 सेकंड लगते हैं।
dbo से DISTINCT V.BountyAmount चुनें। V.BountyAmountOPTION (MAXDOP 1) के अनुसार VORDER के रूप में वोट;ऑप्टिमाइज़र एक कॉलम स्टोर इंडेक्स स्कैन सहित पूरी तरह से बैच मोड प्लान चुनता है:
सीरियल कॉलम स्टोर प्लान
यह 115ms . में चलता है CPU समय की समान मात्रा का उपयोग करना। अनुकूलक मेरे सिस्टम कॉन्फ़िगरेशन पर बिना किसी संकेत के इस योजना को चुनता है क्योंकि योजना की अनुमानित लागत समानता के लिए लागत सीमा से कम है ।
समानांतर योजना प्राप्त करने के लिए, हम या तो लागत सीमा को कम कर सकते हैं या एक गैर-दस्तावेज संकेत का उपयोग कर सकते हैं:
dbo से DISTINCT V.BountyAmount का चयन करें। V.BountyAmountOPTION के अनुसार VORDER के रूप में वोट (उपयोग संकेत ('ENABLE_PARALLEL_PLAN_PREFERENCE'));किसी भी मामले में, समानांतर योजना है:
समानांतर कॉलम स्टोर प्लान
क्वेरी का बीता हुआ समय अब घटकर 30ms हो गया है , 210ms CPU की खपत करते हुए।
5. पुश डाउन के साथ कॉलम स्टोर पर बैच मोड
30ms का वर्तमान सर्वश्रेष्ठ निष्पादन समय प्रभावशाली है, खासकर जब मूल 10,500ms की तुलना में। फिर भी, यह थोड़ी शर्म की बात है कि हमें स्कैन से हैश मैच एग्रीगेट तक लगभग 53 मिलियन पंक्तियों (58,868 बैचों में) को पास करना है।
यह अच्छा होगा यदि SQL सर्वर एकत्रीकरण को स्कैन में नीचे धकेल सकता है और सीधे कॉलम स्टोर से अलग मान लौटा सकता है। आप सोच सकते हैं कि हमें
DISTINCT
. को व्यक्त करने की आवश्यकता हैGROUP BY
. के रूप में समूहीकृत कुल पुशडाउन प्राप्त करने के लिए, लेकिन यह तार्किक रूप से बेमानी है और किसी भी मामले में पूरी कहानी नहीं है।वर्तमान SQL सर्वर कार्यान्वयन के साथ, हमें वास्तव में एक समुच्चय की गणना . करने की आवश्यकता है कुल पुशडाउन को सक्रिय करने के लिए। इसके अलावा, हमें उपयोग . करने की आवश्यकता है कुल परिणाम किसी भी तरह, या अनुकूलक इसे अनावश्यक रूप से हटा देगा।
समग्र पुशडाउन प्राप्त करने के लिए क्वेरी लिखने का एक तरीका तार्किक रूप से निरर्थक द्वितीयक क्रम आवश्यकता को जोड़ना है:
चुनें V.BountyAmountFROM dbo.Votes as V GROUP by V.BountyAmountORDER by V.BountyAmount, COUNT_BIG(*) -- New!OPTION (MAXDOP 1);धारावाहिक योजना अब है:
एग्रीगेट पुश डाउन के साथ सीरियल कॉलम स्टोर प्लान
ध्यान दें कि स्कैन से एग्रीगेट में कोई पंक्तियाँ पास नहीं की जाती हैं! कवर के तहत,
BountyAmount
. का आंशिक योग मान और उनकी संबद्ध पंक्ति गणना को हैश मैच एग्रीगेट को पास कर दिया जाता है, जो आवश्यक अंतिम (वैश्विक) समुच्चय बनाने के लिए आंशिक समुच्चय का योग करता है। यह बहुत ही कुशल है, जैसा कि 13ms . के बीते हुए समय से पुष्टि होती है (जिनमें से सभी CPU समय है)। एक अनुस्मारक के रूप में, पिछली धारावाहिक योजना में 115ms लगे।सेट को पूरा करने के लिए, हम पहले की तरह एक समानांतर संस्करण प्राप्त कर सकते हैं:
एग्रीगेट पुश डाउन के साथ पैरेलल कॉलम स्टोर प्लान
यह 7ms . तक चलता है कुल 40ms CPU का उपयोग करना।
यह शर्म की बात है कि हमें कुल मिलाकर गणना करने और उपयोग करने की आवश्यकता है, हमें केवल पुश डाउन करने की आवश्यकता नहीं है। शायद भविष्य में इसमें सुधार किया जाएगा ताकि
DISTINCT
औरGROUP BY
बिना समुच्चय के स्कैन के लिए नीचे धकेला जा सकता है।6. रो मोड रिकर्सिव कॉमन टेबल एक्सप्रेशन
शुरुआत में, मैंने बड़े डेटा सेट में कम संख्या में डुप्लिकेट खोजने के लिए उपयोग किए जाने वाले पुनरावर्ती CTE समाधान पर फिर से जाने का वादा किया था। उस तकनीक का उपयोग करके वर्तमान आवश्यकता को लागू करना काफी सरल है, हालांकि कोड आवश्यक रूप से हमारे द्वारा अब तक देखी गई किसी भी चीज़ से अधिक लंबा है:
आर एएस के साथ (- डीबीओ से एंकर चुनें वी। बाउंटीअमाउंट। वी ऑर्डर के रूप में वोट। बाउंटीअमाउंट एएससी ऑफसेट 0 पंक्तियाँ पहली 1 पंक्ति केवल यूनियन सभी प्राप्त करें - रिकर्सिव चयन Q1। से इनाम राशि ( चयन वी। इनाम राशि, rn =ROW_NUMBER () ओवर (V.BountyAmount ASC द्वारा ऑर्डर) R JOIN dbo से। V.V पर V.BountyAmount> ISNULL(R.BountyAmount, -1) के रूप में वोट्स Q1 के रूप में Q1.rn =1) R.BountyAmountFROM से चुनें R.BountyAmount ASCOPTION द्वारा RORDER (MAXRECURSION 0);विचार की जड़ें तथाकथित इंडेक्स स्किप स्कैन में हैं:हम आरोही-क्रम वाले बी-ट्री इंडेक्स की शुरुआत में ब्याज का सबसे कम मूल्य पाते हैं, फिर इंडेक्स ऑर्डर में अगला मान ढूंढना चाहते हैं, और इसी तरह। बी-ट्री इंडेक्स की संरचना अगले उच्चतम मूल्य को बहुत कुशल बनाती है - डुप्लिकेट के माध्यम से स्कैन करने की कोई आवश्यकता नहीं है।
यहां एकमात्र वास्तविक तरकीब ऑप्टिमाइज़र को हमें
TOP
. का उपयोग करने की अनुमति देने के लिए मना रही है सीटीई के 'पुनरावर्ती' भाग में प्रत्येक विशिष्ट मूल्य की एक प्रति वापस करने के लिए। यदि आपको विवरण पर पुनश्चर्या की आवश्यकता है तो कृपया मेरा पिछला लेख देखें।निष्पादन योजना (यहां क्रेग फ्रीडमैन द्वारा सामान्य रूप से समझाया गया है) है:
सीरियल रिकर्सिव सीटीई
क्वेरी 1ms . में सही परिणाम देती है संतरी वन प्लान एक्सप्लोरर के अनुसार, 1ms CPU का उपयोग करना।
7. पुनरावृत्त टी-एसक्यूएल
इसी तरह के तर्क को
WHILE
. का उपयोग करके व्यक्त किया जा सकता है कुंडली। पुनरावर्ती सीटीई की तुलना में कोड को पढ़ना और समझना आसान हो सकता है। यह सीटीई के पुनरावर्ती भाग पर कई प्रतिबंधों के आसपास काम करने के लिए तरकीबों का उपयोग करने से भी बचता है। प्रदर्शन लगभग 15ms पर प्रतिस्पर्धी है। यह कोड रुचि के लिए प्रदान किया गया है और प्रदर्शन सारांश तालिका में शामिल नहीं है।नोकाउंट ऑन सेट करें;सेट स्टैटिस्टिक्स एक्सएमएल ऑफ; DECLARE @Result तालिका (बाउंटीअमाउंट पूर्णांक NULL UNIQUE CLUSTERED); DECLARE @BountyAmount पूर्णांक; - इंडेक्स ऑर्डर में पहला मान U AS के साथ (dbo से चुनें V.BountyAmount। V.BountyAmount ASC OFFSET द्वारा V ऑर्डर के रूप में वोट 0 ROWS FETCH NEXT 1 ROW केवल) UPET USET @BountyAmount =U.BountyAmountOUTPUT डाला गया। इनाम राशि); -- अगला उच्च मानजबकि @@ROWCOUNT> 0BEGIN with U AS ( चुनें V.BountyAmount from dbo.Votes as V जहां V.BountyAmount> ISNULL(@BountyAmount, -1) V.BountyAmount ASC OFFSET द्वारा ऑर्डर करें 0 पंक्तियाँ अगली 1 पंक्ति प्राप्त करें केवल ) U SET @BountyAmount =U.BountyAmount OUTPUT सम्मिलित करें। @Result (BountyAmount) में बाउंटीअमाउंट अपडेट करें;END; -- संचित परिणाम चुनें R.BountyAmountFROM @Result AS R ORDER BY R.BountyAmount;प्रदर्शन सारांश तालिका
प्रदर्शन सारांश तालिका (अवधि/मिलीसेकंड में CPU)
"स्था. लागत” कॉलम प्रत्येक क्वेरी के लिए अनुकूलक का लागत अनुमान दिखाता है जैसा कि परीक्षण प्रणाली पर रिपोर्ट किया गया है।
अंतिम विचार
अलग-अलग मानों की एक छोटी संख्या ढूँढना काफी विशिष्ट आवश्यकता की तरह लग सकता है, लेकिन मैं इसे वर्षों से काफी बार आया हूं, आमतौर पर एक बड़ी क्वेरी को ट्यून करने के हिस्से के रूप में।
पिछले कई उदाहरण प्रदर्शन में काफी करीब थे। प्राथमिकताओं के आधार पर, बहुत से लोग उप-सेकंड के किसी भी परिणाम से खुश होंगे। यहां तक कि 2,600ms के रो स्टोर पर सीरियल बैच मोड की तुलना सबसे अच्छे समानांतर से की जाती है पंक्ति मोड योजनाएं, जो केवल SQL सर्वर 2019 में अपग्रेड करके और डेटाबेस संगतता स्तर 150 को सक्षम करके महत्वपूर्ण गति-अप के लिए अच्छा है। हर कोई कॉलम स्टोर स्टोरेज में जल्दी से नहीं जा पाएगा, और यह हमेशा सही समाधान नहीं होगा। . पंक्ति स्टोर पर बैच मोड कॉलम स्टोर के साथ संभव कुछ लाभ प्राप्त करने का एक साफ तरीका प्रदान करता है, यह मानते हुए कि आप ऑप्टिमाइज़र को इसका उपयोग करने के लिए मना सकते हैं।
समानांतर स्तंभ संग्रह 57 मिलियन पंक्तियों . के कुल पुश डाउन परिणाम को संग्रहीत करता है 7ms . में संसाधित किया गया (40ms CPU का उपयोग करना) उल्लेखनीय है, विशेष रूप से हार्डवेयर को देखते हुए। 13ms . का सीरियल कुल पुश डाउन परिणाम समान प्रभावशाली है। यह बहुत अच्छा होगा यदि मुझे इन योजनाओं को प्राप्त करने के लिए एक अर्थहीन समग्र परिणाम न जोड़ना पड़े।
उन लोगों के लिए जो अभी तक SQL सर्वर 2019 या कॉलम स्टोर स्टोरेज में कदम नहीं उठा सकते हैं, रिकर्सिव सीटीई अभी भी व्यवहार्य और कुशल समाधान है जब एक उपयुक्त बी-ट्री इंडेक्स मौजूद है, और आवश्यक विशिष्ट मूल्यों की संख्या काफी कम होने की गारंटी है। यह साफ-सुथरा होगा यदि SQL सर्वर एक पुनरावर्ती CTE (या समतुल्य पुनरावृत्त लूपिंग T-SQL कोड
WHILE
का उपयोग करके लिखने की आवश्यकता के बिना इस तरह के b-पेड़ तक पहुंच सकता है) )।समस्या का एक अन्य संभावित समाधान अनुक्रमित दृश्य बनाना है। यह महान दक्षता के साथ विशिष्ट मूल्य प्रदान करेगा। नीचे की ओर, हमेशा की तरह, अंतर्निहित तालिका में प्रत्येक परिवर्तन को भौतिक दृश्य में संग्रहीत पंक्ति गणना को अद्यतन करने की आवश्यकता होगी।
आवश्यकताओं के आधार पर यहां प्रस्तुत प्रत्येक समाधान का अपना स्थान है। उपलब्ध उपकरणों की एक विस्तृत श्रृंखला आम तौर पर प्रश्नों को ट्यून करते समय एक अच्छी बात कह रही है। अधिकांश समय, मैं बैच मोड समाधानों में से एक को चुनूंगा क्योंकि उनका प्रदर्शन काफी अनुमानित होगा, चाहे कितने भी डुप्लिकेट मौजूद हों।