संख्या श्रृंखला जनरेटर चुनौती के समाधान को कवर करने वाली श्रृंखला का यह पांचवां और अंतिम भाग है। भाग 1, भाग 2, भाग 3 और भाग 4 में मैंने शुद्ध T-SQL समाधानों को शामिल किया। शुरुआत में जब मैंने पहेली पोस्ट की, तो कई लोगों ने टिप्पणी की कि सबसे अच्छा प्रदर्शन करने वाला समाधान संभवतः सीएलआर-आधारित होगा। इस लेख में हम इस सहज धारणा का परीक्षण करेंगे। विशेष रूप से, मैं कामिल कोस्नो और एडम मचानिक द्वारा पोस्ट किए गए सीएलआर-आधारित समाधानों को कवर करूंगा।
अपने विचारों और टिप्पणियों को साझा करने के लिए एलन बर्स्टीन, जो ओबिश, एडम मचानिक, क्रिस्टोफर फोर्ड, जेफ मोडन, चार्ली, नोआमजीआर, कामिल कोस्नो, डेव मेसन, जॉन नेल्सन #2, एड वैगनर, माइकल बरबी और पॉल व्हाइट को बहुत-बहुत धन्यवाद।पी>
मैं अपना परीक्षण testdb नामक डेटाबेस में करूँगा। यदि डेटाबेस मौजूद नहीं है तो डेटाबेस बनाने के लिए और I/O और समय के आंकड़े सक्षम करने के लिए निम्न कोड का उपयोग करें:
-- DB और statsSET NOCOUNT ON;SET Statistics IO, TIME ON;GO IF DB_ID('testdb') IS NULL CREATE DATABASE testdb;GO USE testdb;GO
सादगी के लिए, मैं सीएलआर सख्त सुरक्षा को अक्षम कर दूंगा और निम्नलिखित कोड का उपयोग करके डेटाबेस को भरोसेमंद बना दूंगा:
-- सीएलआर सक्षम करें, सीएलआर सख्त सुरक्षा अक्षम करें और डीबी भरोसेमंद बनाएंEXEC sys.sp_configure 'उन्नत सेटिंग्स दिखाएं', 1;RECONFIGURE; EXEC sys.sp_configure 'clr enable', 1;EXEC sys.sp_configure 'clr सख्त सुरक्षा', 0;RECONFIGURE; EXEC sys.sp_configure 'उन्नत सेटिंग्स दिखाएं', 0;RECONFIGURE; ALTER DATABASE testdb SET TRUSTWORTHY ON; जाओ
पहले के समाधान
इससे पहले कि मैं सीएलआर-आधारित समाधानों को कवर करूं, आइए दो सबसे अच्छा प्रदर्शन करने वाले टी-एसक्यूएल समाधानों के प्रदर्शन की तुरंत समीक्षा करें।
सबसे अच्छा प्रदर्शन करने वाला टी-एसक्यूएल समाधान जो किसी भी स्थायी बेस टेबल (बैच प्रोसेसिंग प्राप्त करने के लिए डमी खाली कॉलमस्टोर टेबल के अलावा) का उपयोग नहीं करता था, और इसलिए कोई I/O संचालन शामिल नहीं था, वह dbo.GetNumsAlanCharlieItzikBatch में कार्यान्वित किया गया था। मैंने इस समाधान को भाग 1 में शामिल किया है।
फ़ंक्शन की क्वेरी द्वारा उपयोग की जाने वाली डमी खाली कॉलमस्टोर तालिका बनाने के लिए यहां कोड है:
ड्रॉप टेबल अगर मौजूद है dbo.BatchMe;GO CREATE TABLE dbo.BatchMe(col1 INT NOT NULL, INDEX idx_cs CLUSTERED COLUMNSTORE);जाओ
और यहाँ फ़ंक्शन की परिभाषा के साथ कोड है:
क्रिएट या अल्टर फंक्शन dbo.GetNumsAlanCharlieItzikBatch(@low AS BIGINT =1, @high AS BIGINT) L0 AS के साथ TABLEASRETURN लौटाता है (से 1 AS c चुनें (VALUES(1),(1),(1),(1) ),(1),(1),(1),(1), (1),(1),(1),(1),(1),(1),(1),(1)) एएस डी (सी)), एल 1 एएस (एक क्रॉस जॉइन एल 0 एएस बी के रूप में एल 0 से सी चुनें), एल 2 एएस (एल 1 से एक क्रॉस जॉइन एल 1 एएस बी के रूप में चुनें), एल 3 एएस (चुनें 1 एएस सी चुनें) L2 से एक क्रॉस जॉइन L2 AS B के रूप में), नंबर्स AS (सिलेक्ट ROW_NUMBER() ओवर (ऑर्डर बाय (सिलेक्ट NULL)) AS Rownum L3 से) सेलेक्ट टॉप (@high - @low + 1) rownum AS rn, @high + 1 - राउनम एएस ऑप, @लो - 1 + राउनम एएस एन फ्रॉम नंबर्स लेफ्ट आउटर जॉइन dbo.BatchMe ON 1 =0 Rownum द्वारा ऑर्डर करें;GO
आइए पहले 100M संख्याओं की एक श्रृंखला का अनुरोध करने वाले फ़ंक्शन का परीक्षण करें, जिसमें MAX कुल कॉलम n पर लागू होता है:
dbo से MAX(n) AS mx चुनें।GetNumsAlanCharlieItzikBatch(1, 100000000) OPTION(MAXDOP 1);
याद रखें, यह परीक्षण तकनीक कॉलर को 100M पंक्तियों को प्रेषित करने से बचाती है, और वेरिएबल असाइनमेंट तकनीक का उपयोग करते समय वेरिएबल असाइनमेंट में शामिल पंक्ति-मोड प्रयास से भी बचाती है।
इस परीक्षण के लिए मुझे अपनी मशीन पर मिले समय के आंकड़े यहां दिए गए हैं:
CPU समय =6719 ms, बीता हुआ समय =6742 ms .इस फ़ंक्शन का निष्पादन निश्चित रूप से कोई तार्किक पठन उत्पन्न नहीं करता है।
इसके बाद, इसे वेरिएबल असाइनमेंट तकनीक का उपयोग करके क्रम के साथ परीक्षण करते हैं:
घोषित @n के रूप में BIGINT; dbo से @n =n चुनें।मुझे इस निष्पादन के लिए निम्नलिखित समय आँकड़े मिले:
CPU समय =9468 ms, बीता हुआ समय =9531 ms .याद रखें कि n द्वारा ऑर्डर किए गए डेटा का अनुरोध करते समय यह फ़ंक्शन सॉर्टिंग में परिणत नहीं होता है; आप मूल रूप से वही योजना प्राप्त करते हैं चाहे आप ऑर्डर किए गए डेटा का अनुरोध करें या नहीं। हम पिछले एक की तुलना में इस परीक्षण में अधिकांश अतिरिक्त समय को 100M पंक्ति-मोड आधारित चर असाइनमेंट के लिए जिम्मेदार ठहरा सकते हैं।
सबसे अच्छा प्रदर्शन करने वाला टी-एसक्यूएल समाधान जो एक स्थायी आधार तालिका का उपयोग करता था, और इसलिए कुछ I/O संचालन में परिणामित होता था, हालांकि बहुत कम, पॉल व्हाइट का समाधान dbo.GetNums_SQLkiwi फ़ंक्शन में लागू किया गया था। मैंने इस समाधान को भाग 4 में शामिल किया है।
फ़ंक्शन और फ़ंक्शन द्वारा उपयोग की जाने वाली कॉलमस्टोर तालिका दोनों को बनाने के लिए पॉल का कोड यहां दिया गया है:
-- हेल्पर कॉलमस्टोर टेबलड्रॉप टेबल अगर मौजूद है dbo.CS; - 64K पंक्तियाँ (क्रॉस में शामिल होने पर 4B पंक्तियों के लिए पर्याप्त) - कॉलम 1 हमेशा शून्य होता है - कॉलम 2 (1...65536) होता है - पूर्णांक के रूप में टाइप करें NOT NULL - (सब कुछ 64 बिट्स में सामान्यीकृत होता है कॉलमस्टोर/बैच मोड वैसे भी) n1 =ISNULL (कन्वर्ट (पूर्णांक, 0)), n2 =ISNULL (कन्वर्ट (पूर्णांक, N.rn), 0) डीबीओ में। @@ SPID) मास्टर.dbo.spt_values से SV1 क्रॉस के रूप में शामिल हों master.dbo.spt_values के रूप में RN ASC ऑफ़सेट द्वारा SV2 ऑर्डर के रूप में 0 पंक्तियाँ फ़ेच अगले 65536 पंक्तियाँ केवल) N के रूप में; - 65,536 पंक्तियों का सिंगल कंप्रेस्ड रोग्रुप CREATE COLUSTERED COLUMNSTORE INDEX CCI ON dbo.CS विद (MAXDOP =1); GO - फंक्शनक्रिएट या अल्टर फंक्शन dbo.GetNums_SQLkiwi(@low bigint =1, @high bigint) रिटर्न्स टेबल चुनें। .rn, n =@low - 1 + N.rn, op =@high + 1 - N.rn FROM ( सेलेक्ट - यदि आप अपने सभी प्रश्नों को पसंद करते हैं तो @@ SPID के बजाय @@ TRANCOUNT का उपयोग करें rn =ROW_NUMBER() ओवर (ऑर्डर बाय @@SPID ASC) dbo.CS से N1 के रूप में शामिल हों dbo.CS AS N2 में शामिल हों - बैच मोड हैश क्रॉस जॉइन - पूर्णांक नहीं डेटा प्रकार हैश जांच अवशिष्ट से बचें - यह हमेशा N2 पर 0 =0 होता है। n1 =N1.n1 जहां - नकारात्मक संख्याओं पर SQRT से बचने और सरलीकरण को सक्षम करने का प्रयास करें - एकल निरंतर स्कैन के लिए यदि @low> @high (शाब्दिक के साथ) - बैच मोड में कोई स्टार्ट-अप फ़िल्टर नहीं @high>=@low -- मोटे फिल्टर:-- . के प्रत्येक पक्ष को सीमित करें क्रॉस एसक्यूआरटी (पंक्तियों की लक्ष्य संख्या) में शामिल हो जाता है - आईआईएफ पैरामीटर के साथ नकारात्मक संख्याओं पर एसक्यूआरटी से बचाता है और एन 1.एन 2 <=कन्वर्ट (पूर्णांक, सीलिंग (एसक्यूआरटी (कन्वर्ट (फ्लोट, आईआईएफ (@ उच्च> =@ कम, @ उच्च)) - @low + 1, 0))))) और N2.n2 <=CONVERT (पूर्णांक, सीलिंग (SQRT (कन्वर्ट (फ्लोट, IIF (@high> =@low, @high - @low + 1, 0))) )))) एन जहां के रूप में - सटीक फिल्टर:- बैच मोड सीमित क्रॉस जॉइन को आवश्यक पंक्तियों की सटीक संख्या में फ़िल्टर करता है - ऑप्टिमाइज़र से एक पंक्ति-मोड शुरू करने से बचता है शीर्ष निम्न पंक्ति मोड के साथ स्केलर @low - 2 + की गणना करें N.rn <@high;GOआइए पहले समग्र तकनीक का उपयोग किए बिना इसका परीक्षण करें, जिसके परिणामस्वरूप एक ऑल-बैच-मोड-प्लान:
dbo से MAX(n) AS mx चुनें।GetNums_SQLkiwi(1, 100000000) OPTION(MAXDOP 1);मुझे इस निष्पादन के लिए निम्नलिखित समय और I/O आँकड़े मिले:
CPU समय =2922 ms, बीता हुआ समय =2943 ms .
टेबल 'सीएस'। स्कैन काउंट 2, लॉजिकल रीड्स 0, फिजिकल रीड्स 0, पेज सर्वर रीड्स 0, रीड-फॉरवर्ड रीड्स 0, पेज सर्वर रीड-आगे रीड्स 0, लॉब लॉजिकल रीड्स 44 , लॉब भौतिक 0 पढ़ता है, लॉब पृष्ठ सर्वर 0 पढ़ता है, लोब आगे पढ़ता है 0, लोब पृष्ठ सर्वर आगे पढ़ता है 0.
तालिका 'सीएस'। खंड 2 पढ़ता है, खंड 0 छोड़ दिया गया है।आइए चर असाइनमेंट तकनीक का उपयोग करके क्रम के साथ फ़ंक्शन का परीक्षण करें:
घोषित @n के रूप में BIGINT; dbo से @n =n चुनें। GetNums_SQLkiwi(1, 100000000) n विकल्प द्वारा आदेश (MAXDOP 1);पिछले समाधान की तरह, यह समाधान भी योजना में स्पष्ट छँटाई से बचता है, और इसलिए वही योजना प्राप्त करता है चाहे आप ऑर्डर किए गए डेटा के लिए पूछें या नहीं। लेकिन फिर से, इस परीक्षण में मुख्य रूप से यहां उपयोग की जाने वाली चर असाइनमेंट तकनीक के कारण एक अतिरिक्त जुर्माना लगता है, जिसके परिणामस्वरूप योजना में चर असाइनमेंट भाग को पंक्ति मोड में संसाधित किया जाता है।
इस निष्पादन के लिए मुझे जो समय और I/O आँकड़े मिले हैं, वे हैं:
CPU समय =6985 ms, बीता हुआ समय =7033 ms .
टेबल 'सीएस'। स्कैन काउंट 2, लॉजिकल रीड्स 0, फिजिकल रीड्स 0, पेज सर्वर रीड्स 0, रीड-फॉरवर्ड रीड्स 0, पेज सर्वर रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 44 , लॉब भौतिक 0 पढ़ता है, लॉब पृष्ठ सर्वर 0 पढ़ता है, लोब आगे पढ़ता है 0, लोब पृष्ठ सर्वर आगे पढ़ता है 0.
तालिका 'सीएस'। खंड 2 पढ़ता है, खंड 0 छोड़ दिया गया है।CLR समाधान
कामिल कोस्नो और एडम मचानिक दोनों ने पहले एक सरल सीएलआर-ओनली समाधान प्रदान किया, और बाद में एक अधिक परिष्कृत सीएलआर + टी-एसक्यूएल कॉम्बो के साथ आया। मैं कामिल के समाधानों के साथ शुरुआत करूँगा और फिर एडम के समाधानों को कवर करूँगा।
कामिल कोस्नो द्वारा समाधान
GetNums_KamilKosno1 नामक फ़ंक्शन को परिभाषित करने के लिए कामिल के पहले समाधान में उपयोग किया गया सीएलआर कोड यहां दिया गया है:
सिस्टम का उपयोग करना; सिस्टम का उपयोग करना। (SqlInt64 कम, SqlInt64 उच्च) {वापसी (निम्न। IsNull || high.IsNull)? नया GetNumsCS(0, 0) :new GetNumsCS(low.Value, high.Value); } सार्वजनिक स्थैतिक शून्य GetNums_KamilKosno1_Fill (ऑब्जेक्ट ओ, आउट SqlInt64 n) { n =(लंबा) ओ; } निजी वर्ग GetNumsCS:IEnumerator {सार्वजनिक GetNumsCS (लंबे समय से, लंबे समय तक) { _lowrange =from; _वर्तमान =_लोरेंज - 1; _उच्च श्रेणी =से; } पब्लिक बूल मूवनेक्स्ट () { _current +=1; अगर (_current> _highrange) झूठी वापसी करता है; अन्यथा सच लौटो; } सार्वजनिक वस्तु वर्तमान { प्राप्त करें {वापसी _current; } } सार्वजनिक शून्य रीसेट () { _current =_lowrange - 1; } लंबी _लोरेंज; लंबा _वर्तमान; लंबी _उच्च श्रेणी; }}फ़ंक्शन निम्न और उच्च नामक दो इनपुट स्वीकार करता है और n नामक एक BIGINT कॉलम के साथ एक तालिका देता है। फ़ंक्शन एक स्ट्रीमिंग प्रकार है, कॉलिंग क्वेरी से श्रृंखला प्रति पंक्ति अनुरोध में अगली संख्या के साथ एक पंक्ति लौटाता है। जैसा कि आप देख सकते हैं, कामिल ने IEnumerator इंटरफ़ेस को लागू करने की अधिक औपचारिक विधि को चुना, जिसमें मूवनेक्स्ट (अगली पंक्ति प्राप्त करने के लिए एन्यूमरेटर को आगे बढ़ाता है), करंट (वर्तमान एन्यूमरेटर स्थिति में पंक्ति प्राप्त करता है), और रीसेट (सेट) को लागू करना शामिल है। गणक अपनी प्रारंभिक स्थिति में, जो पहली पंक्ति से पहले है)।
श्रृंखला में वर्तमान संख्या को धारण करने वाले चर को _current कहा जाता है। कंस्ट्रक्टर _current को अनुरोधित सीमा माइनस 1 की निचली सीमा पर सेट करता है, और वही रीसेट विधि के लिए जाता है। मूवनेक्स्ट विधि _current को 1 से आगे बढ़ाती है। फिर, यदि _current अनुरोधित सीमा की उच्च सीमा से अधिक है, तो विधि झूठी वापसी करती है, जिसका अर्थ है कि इसे फिर से नहीं कहा जाएगा। अन्यथा, यह सच हो जाता है, जिसका अर्थ है कि इसे फिर से बुलाया जाएगा। वर्तमान विधि स्वाभाविक रूप से _current लौटाती है। जैसा कि आप देख सकते हैं, काफी बुनियादी तर्क।
मैंने विजुअल स्टूडियो प्रोजेक्ट GetNumsKamil1 को कॉल किया, और इसके लिए पथ C:\Temp\ का उपयोग किया। यहाँ वह कोड है जिसका उपयोग मैंने टेस्टडब डेटाबेस में फ़ंक्शन को परिनियोजित करने के लिए किया था:
ड्रॉप फ़ंक्शन यदि मौजूद है dbo.GetNums_KamilKosno1; यदि GetNumsKamil1 मौजूद है तो असेंबली छोड़ें; असेंबली बनाएं जाओ 'C:\Temp\GetNumsKamil1\GetNumsKamil1\bin\Debug\GetNumsKamil1.dll' से GetNumsKamil1 प्राप्त करें; GO CREATE FUNCTION dbo.GetNos1_KamilK कम ASIATE FUNCTION dbo.GetNos1_KamilK(@KamilK) TABLE(n BIGINT) ORDER(n) जैसा कि बाहरी नाम GetNumsKamil1.GetNumsKamil1.GetNums_KamilKosno1;GOCREATE FUNCTION स्टेटमेंट में ORDER क्लॉज के उपयोग पर ध्यान दें। फ़ंक्शन n क्रम में पंक्तियों का उत्सर्जन करता है, इसलिए जब पंक्तियों को n क्रम में योजना में सम्मिलित करने की आवश्यकता होती है, तो इस खंड के आधार पर SQL सर्वर जानता है कि यह योजना में एक प्रकार से बच सकता है।
आइए फ़ंक्शन का परीक्षण करें, पहले समग्र तकनीक के साथ, जब ऑर्डर करने की आवश्यकता नहीं होती है:
dbo से MAX(n) AS mx चुनें।GetNums_KamilKosno1(1, 100000000);मुझे चित्र 1 में दिखाया गया प्लान मिला है।
चित्र 1:dbo.GetNums_KamilKosno1 फ़ंक्शन के लिए योजना
इस योजना के बारे में कहने के लिए बहुत कुछ नहीं है, सिवाय इसके कि सभी ऑपरेटर पंक्ति निष्पादन मोड का उपयोग करते हैं।
मुझे इस निष्पादन के लिए निम्नलिखित समय आँकड़े मिले:
CPU समय =37375 ms, बीता हुआ समय =37488 ms .और निश्चित रूप से, कोई तार्किक पठन शामिल नहीं था।
आइए चर असाइनमेंट तकनीक का उपयोग करके क्रम के साथ फ़ंक्शन का परीक्षण करें:
घोषित @n के रूप में BIGINT; dbo से @n =n चुनें। GetNums_KamilKosno1 (1, 100000000) n द्वारा ऑर्डर करें;मुझे इस निष्पादन के लिए चित्र 2 में दिखाया गया प्लान मिला है।
चित्र 2:ऑर्डर के साथ dbo.GetNums_KamilKosno1 फ़ंक्शन के लिए योजना
ध्यान दें कि ORDER(n) क्लॉज के साथ फ़ंक्शन बनाए जाने के बाद से योजना में कोई छँटाई नहीं है। हालांकि, यह सुनिश्चित करने के लिए कुछ प्रयास हैं कि पंक्तियों को वास्तव में फ़ंक्शन से वादा किए गए क्रम में उत्सर्जित किया जाता है। यह सेगमेंट और सीक्वेंस प्रोजेक्ट ऑपरेटरों का उपयोग करके किया जाता है, जिनका उपयोग पंक्ति संख्याओं की गणना करने के लिए किया जाता है, और Assert ऑपरेटर, जो परीक्षण विफल होने पर क्वेरी निष्पादन को रोक देता है। इस काम में रैखिक स्केलिंग है - एन लॉग एन स्केलिंग के विपरीत आपको एक प्रकार की आवश्यकता होती है - लेकिन यह अभी भी सस्ता नहीं है। मुझे इस परीक्षण के लिए निम्नलिखित समय आँकड़े मिले हैं:
CPU समय =51531 ms, बीता हुआ समय =51905 ms .परिणाम कुछ लोगों के लिए आश्चर्यजनक हो सकते हैं - विशेष रूप से उन लोगों के लिए जिन्होंने सहज रूप से यह मान लिया था कि सीएलआर-आधारित समाधान टी-एसक्यूएल वाले की तुलना में बेहतर प्रदर्शन करेंगे। जैसा कि आप देख सकते हैं, निष्पादन समय हमारे सर्वोत्तम प्रदर्शन करने वाले टी-एसक्यूएल समाधान की तुलना में अधिक परिमाण का क्रम है।
कामिल का दूसरा समाधान सीएलआर-टी-एसक्यूएल हाइब्रिड है। निम्न और उच्च इनपुट से परे, CLR फ़ंक्शन (GetNums_KamilKosno2) एक चरण इनपुट जोड़ता है, और निम्न और उच्च के बीच मान देता है जो एक दूसरे से अलग होते हैं। यहाँ CLR कोड कामिल ने अपने दूसरे समाधान में उपयोग किया है:
System का उपयोग करना;System.Data.SqlTypes का उपयोग करना;System.Collections का उपयोग करना; सार्वजनिक आंशिक वर्ग GetNumsKamil2 { [Microsoft.SqlServer.Server.SqlFunction (DataAccess =Microsoft.SqlServer.Server.DataAccessKind.None, IsDeterministic =true, IsPrecise =true, FillRowMethodName ="GetNums_Fill", TableDefinition ="n BIGINT")] सार्वजनिक स्थैतिक IEnumerator GetNums_KamilKosno2 (SqlInt64 कम, SqlInt64 उच्च, SqlInt64 चरण) {वापसी (निम्न। IsNull || high.IsNull)? नया GetNumsCS(0, 0, step.Value):नया GetNumsCS(low.Value, high.Value, step.Value); } सार्वजनिक स्थैतिक शून्य GetNums_Fill (ऑब्जेक्ट ओ, आउट SqlInt64 n) { n =(लंबा) ओ; } निजी वर्ग GetNumsCS:IEnumerator {सार्वजनिक GetNumsCS (लंबे समय से, लंबा, लंबा कदम) { _lowrange =from; _कदम =कदम; _वर्तमान =_लोरेंज - _स्टेप; _उच्च श्रेणी =से; } पब्लिक बूल मूवनेक्स्ट () { _current =_current + _step; अगर (_current> _highrange) झूठी वापसी करता है; अन्यथा सच लौटो; } सार्वजनिक वस्तु वर्तमान { प्राप्त करें {वापसी _current; } } सार्वजनिक शून्य रीसेट () { _current =_lowrange - _step; } लंबी _लोरेंज; लंबा _वर्तमान; लंबी _उच्च श्रेणी; लंबा कदम; }}मैंने VS प्रोजेक्ट का नाम GetNumsKamil2 रखा, इसे पथ C:\Temp\ में भी रखा, और इसे testdb डेटाबेस में परिनियोजित करने के लिए निम्न कोड का उपयोग किया:
-- यदि मौजूद है तो असेंबली और फंक्शनड्रॉप फंक्शन बनाएं। .GetNums_KamilKosno2 (@low AS BIGINT =1, @high AS BIGINT, @step AS BIGINT) रिटर्न टेबल (n BIGINT) ऑर्डर (n) बाहरी नाम के रूप में GetNumsKamil2.GetNumsKamil2.GetNums_KamilKosno2;GOफ़ंक्शन का उपयोग करने के लिए एक उदाहरण के रूप में, यहां 10 के चरण के साथ 5 और 59 के बीच मान उत्पन्न करने का अनुरोध किया गया है:
dbo से चुनें.GetNums_KamilKosno2(5, 59, 10);यह कोड निम्न आउटपुट उत्पन्न करता है:
n----51525354555टी-एसक्यूएल भाग के लिए, कामिल ने निम्नलिखित कोड के साथ dbo.GetNums_Hybrid_Kamil2 नामक एक फ़ंक्शन का उपयोग किया:
क्रिएट या अल्टर फंक्शन dbo.GetNums_Hybrid_Kamil2(@low AS BIGINT, @high AS BIGINT) रिटर्न्स टेबल रिटर्न सेलेक्ट टॉप (@high - @low + 1) V.n FROM dbo.GetNums_KamilKosno2(@low, @high, 10) AS क्रॉस लागू (मान(0+GN.n),(1+GN.n),(2+GN.n),(3+GN.n),(4+GN.n), (5+GN.n) ),(6+GN.n),(7+GN.n),(8+GN.n),(9+GN.n)) AS V(n);GOजैसा कि आप देख सकते हैं, टी-एसक्यूएल फ़ंक्शन सीएलआर फ़ंक्शन को उसी @low और @high इनपुट के साथ आमंत्रित करता है जो इसे प्राप्त होता है, और इस उदाहरण में 10 के चरण आकार का उपयोग करता है। क्वेरी सीएलआर फ़ंक्शन के परिणाम और ए के बीच क्रॉस लागू का उपयोग करती है टेबल-वैल्यू कंस्ट्रक्टर जो चरण की शुरुआत में 0 से 9 की सीमा में मान जोड़कर अंतिम संख्या उत्पन्न करता है। TOP फ़िल्टर का उपयोग यह सुनिश्चित करने के लिए किया जाता है कि आपको आपके द्वारा अनुरोधित संख्या से अधिक न मिले।
महत्वपूर्ण: मुझे इस बात पर जोर देना चाहिए कि कामिल परिणाम संख्या क्रम के आधार पर लागू किए जा रहे TOP फ़िल्टर के बारे में यहाँ एक धारणा बनाता है, जिसकी वास्तव में गारंटी नहीं है क्योंकि क्वेरी में ORDER BY क्लॉज नहीं है। यदि आप या तो TOP का समर्थन करने के लिए ORDER BY क्लॉज जोड़ते हैं, या टॉप को WHERE फ़िल्टर से प्रतिस्थापित करते हैं, तो एक नियतात्मक फ़िल्टर की गारंटी देने के लिए, यह समाधान के प्रदर्शन प्रोफ़ाइल को पूरी तरह से बदल सकता है।
किसी भी दर पर, आइए पहले समग्र तकनीक का उपयोग किए बिना फ़ंक्शन का परीक्षण करें:
dbo से MAX(n) AS mx चुनें।GetNums_Hybrid_Kamil2(1, 100000000);मुझे इस निष्पादन के लिए चित्र 3 में दिखाई गई योजना मिली है।
चित्र 3:dbo.GetNums_Hybrid_Kamil2 फ़ंक्शन के लिए योजना
फिर से, योजना में सभी ऑपरेटर पंक्ति निष्पादन मोड का उपयोग करते हैं।
मुझे इस निष्पादन के लिए निम्नलिखित समय आँकड़े मिले:
CPU समय =13985 ms, बीता हुआ समय =14069 ms .और स्वाभाविक रूप से कोई तार्किक नहीं पढ़ता है।
आइए क्रम के साथ फ़ंक्शन का परीक्षण करें:
घोषित @n के रूप में BIGINT; dbo से @n =n चुनें। GetNums_Hybrid_Kamil2(1, 100000000) n द्वारा ऑर्डर करें;मुझे चित्र 4 में दिखाया गया प्लान मिला है।
चित्र 4:ऑर्डर के साथ dbo.GetNums_Hybrid_Kamil2 फ़ंक्शन के लिए योजना
चूंकि परिणाम संख्याएं सीएलआर फ़ंक्शन द्वारा लौटाए गए चरण की निचली सीमा के हेरफेर का परिणाम हैं और टेबल-वैल्यू कंस्ट्रक्टर में जोड़े गए डेल्टा, ऑप्टिमाइज़र को भरोसा नहीं है कि परिणाम संख्या अनुरोधित क्रम में उत्पन्न होती है, और योजना में स्पष्ट छँटाई जोड़ता है।
मुझे इस निष्पादन के लिए निम्नलिखित समय आँकड़े मिले:
CPU समय =68703 ms, बीता हुआ समय =84538 ms .तो ऐसा लगता है कि जब किसी आदेश की आवश्यकता नहीं होती है, तो कामिल का दूसरा समाधान उनके पहले से बेहतर होता है। लेकिन जब आदेश की जरूरत होती है, तो यह दूसरी तरफ होता है। किसी भी तरह से, टी-एसक्यूएल समाधान तेज हैं। निजी तौर पर, मुझे पहले समाधान की शुद्धता पर भरोसा है, लेकिन दूसरे पर नहीं।
एडम मचानिक द्वारा समाधान
एडम का पहला समाधान भी एक बुनियादी सीएलआर फ़ंक्शन है जो एक काउंटर को बढ़ाता रहता है। केवल कामिल की तरह अधिक शामिल औपचारिक दृष्टिकोण का उपयोग करने के बजाय, एडम ने एक सरल दृष्टिकोण का उपयोग किया जो प्रति पंक्ति उपज कमांड को वापस करने की आवश्यकता है।
यहां उनके पहले समाधान के लिए एडम का सीएलआर कोड है, जो GetNums_AdamMachanic1 नामक एक स्ट्रीमिंग फ़ंक्शन को परिभाषित करता है:
System.Data.SqlTypes का उपयोग करना;System.Collections का उपयोग करना; सार्वजनिक आंशिक वर्ग GetNumsAdam1 { [Microsoft.SqlServer.Server.SqlFunction (FillRowMethodName ="GetNums_AdamMachanic1_fill", TableDefinition ="n BIGINT")] सार्वजनिक स्थैतिक IEnumerable GetNums_AdamMachanic1 (SqlInt64 मिनट, SqlInt64 अधिकतम) { var min_int =min.Value; var max_int =max.Value; के लिए (; min_int <=max_int; min_int++) {उपज वापसी (min_int); } } सार्वजनिक स्थैतिक शून्य GetNums_AdamMachanic1_fill (ऑब्जेक्ट ओ, आउट लॉन्ग i) {i =(लॉन्ग) ओ; }};इसकी सादगी में समाधान इतना सुंदर है। जैसा कि आप देख सकते हैं, फ़ंक्शन अनुरोधित सीमा के निम्न और उच्च सीमा बिंदुओं का प्रतिनिधित्व करने वाले न्यूनतम और अधिकतम नामक दो इनपुट स्वीकार करता है, और n नामक एक BIGINT कॉलम के साथ एक तालिका देता है। फ़ंक्शन संबंधित फ़ंक्शन के इनपुट पैरामीटर मानों के साथ min_int और max_int नामक चर प्रारंभ करता है। फ़ंक्शन तब तक एक लूप चलाता है जब तक कि min_int <=max_int, प्रत्येक पुनरावृत्ति में min_int के वर्तमान मान के साथ एक पंक्ति उत्पन्न करता है और min_int को 1 से बढ़ाता है। बस।
मैंने VS में प्रोजेक्ट GetNumsAdam1 नाम दिया, इसे C:\Temp\ में रखा, और इसे परिनियोजित करने के लिए निम्न कोड का उपयोग किया:
-- अगर मौजूद है तो असेंबली और फंक्शनड्रॉप फंक्शन बनाएं। .GetNums_AdamMachanic1(@low AS BIGINT =1, @high AS BIGINT) रिटर्न टेबल (n BIGINT) ORDER(n) AS बाहरी नाम GetNumsAdam1.GetNumsAdam1.GetNums_AdamMachanic1;GOमैंने निम्नलिखित कोड का उपयोग समग्र तकनीक के साथ इसका परीक्षण करने के लिए किया, ऐसे मामलों के लिए जब ऑर्डर कोई मायने नहीं रखता:
dbo से MAX(n) AS mx चुनें।GetNums_AdamMachanic1(1, 100000000);मुझे इस निष्पादन के लिए चित्र 5 में दिखाया गया प्लान मिला है।
चित्र 5:dbo.GetNums_AdamMachanic1 फ़ंक्शन के लिए योजना
यह योजना उस योजना के समान है जिसे आपने पहले कामिल के पहले समाधान के लिए देखा था, और यह उसके प्रदर्शन पर भी लागू होता है। मुझे इस निष्पादन के लिए निम्नलिखित समय आँकड़े मिले:
CPU समय =36687 ms, बीता हुआ समय =36952 ms .और निश्चित रूप से किसी तार्किक पठन की आवश्यकता नहीं थी।
आइए चर असाइनमेंट तकनीक का उपयोग करके क्रम के साथ फ़ंक्शन का परीक्षण करें:
घोषित @n के रूप में BIGINT; dbo से @n =n चुनें। GetNums_AdamMachanic1(1, 100000000) n द्वारा ऑर्डर करें;मुझे इस निष्पादन के लिए चित्र 6 में दिखाई गई योजना मिली है।
चित्र 6:ऑर्डर के साथ dbo.GetNums_AdamMachanic1 फ़ंक्शन के लिए योजना
फिर, योजना वैसी ही दिखती है जैसी आपने पहले कामिल के पहले समाधान के लिए देखी थी। ORDER क्लॉज के साथ फ़ंक्शन बनाए जाने के बाद से स्पष्ट सॉर्टिंग की कोई आवश्यकता नहीं थी, लेकिन योजना में यह सत्यापित करने के लिए कुछ कार्य शामिल हैं कि पंक्तियों को वास्तव में वादे के अनुसार वापस किया गया है।
मुझे इस निष्पादन के लिए निम्नलिखित समय आँकड़े मिले:
CPU समय =55047 ms, बीता हुआ समय =55498 ms .अपने दूसरे समाधान में, एडम ने एक सीएलआर भाग और एक टी-एसक्यूएल भाग को भी जोड़ा। यहाँ एडम ने अपने समाधान में इस्तेमाल किए गए तर्क का वर्णन किया है:
"मैं यह सोचने की कोशिश कर रहा था कि एसक्यूएलसीएलआर चैटनेस मुद्दे के आसपास कैसे काम किया जाए, और टी-एसक्यूएल में इस नंबर जेनरेटर की केंद्रीय चुनौती भी है, जो तथ्य यह है कि हम केवल जादू की पंक्तियों को अस्तित्व में नहीं रख सकते हैं।सीएलआर दूसरे भाग के लिए एक अच्छा उत्तर है लेकिन निश्चित रूप से पहले अंक से बाधित है। तो एक समझौते के रूप में मैंने एक टी-एसक्यूएल टीवीएफ बनाया [जिसे GetNums_AdamMachanic2_8192 कहा जाता है] 1 से 8192 के मानों के साथ हार्डकोड किया गया। (काफी मनमाना विकल्प, लेकिन बहुत बड़ा और क्यूओ उस पर थोड़ा घुटना शुरू कर देता है।) इसके बाद मैंने अपने सीएलआर फ़ंक्शन को संशोधित किया [ दो कॉलम, "max_base" और "base_add" को आउटपुट करने के लिए GetNums_AdamMachanic2_8192_base] नाम दिया गया है, और इसमें आउटपुट पंक्तियाँ हैं जैसे:
- max_base, base_add
——————
8191, 1
8192, 8192
8192, 16384
…
8192, 99991552<ब्र />257, 99999744
अब यह एक साधारण लूप है। सीएलआर आउटपुट टी-एसक्यूएल टीवीएफ को भेजा जाता है, जिसे केवल इसके हार्डकोडेड सेट की "max_base" पंक्तियों तक लौटने के लिए सेट किया जाता है। और प्रत्येक पंक्ति के लिए, यह मूल्य में "base_add" जोड़ता है, जिससे अपेक्षित संख्याएं उत्पन्न होती हैं। मुझे लगता है कि यहां कुंजी यह है कि हम केवल एक तार्किक क्रॉस जॉइन के साथ एन पंक्तियां उत्पन्न कर सकते हैं, और सीएलआर फ़ंक्शन को केवल 1/8192 को कई पंक्तियों को वापस करना है, इसलिए यह बेस जनरेटर के रूप में कार्य करने के लिए पर्याप्त तेज़ है।
तर्क बहुत सीधा लगता है।
GetNums_AdamMachanic2_8192_base नामक CLR फ़ंक्शन को परिभाषित करने के लिए उपयोग किया जाने वाला कोड यहां दिया गया है:
System.Data.SqlTypes का उपयोग करना;System.Collections का उपयोग करना; सार्वजनिक आंशिक वर्ग GetNumsAdam2 {निजी संरचना पंक्ति {सार्वजनिक लंबा max_base; सार्वजनिक लंबा बेस_एड; } [Microsoft.SqlServer.Server.SqlFunction (FillRowMethodName ="GetNums_AdamMachanic2_8192_base_fill", TableDefinition ="max_base int, base_add int")] सार्वजनिक स्थैतिक IEnumerable GetNums_AdamMachanic2_8192_base (SqlInt64 न्यूनतम) { Sqlvar min_base (SqlInt64 अधिकतम) var max_int =max.Value; वर min_group =min_int / 8192; वर मैक्स_ग्रुप =मैक्स_इंट / 8192; के लिए (; min_group <=max_group; min_group++) { if (min_int> max_int) यील्ड ब्रेक; var max_base =8192 - (min_int% 8192); अगर (min_group ==max_group &&max_int <(((max_int / 8192) + 1) * 8192) - 1) max_base =max_int - min_int + 1; उपज वापसी (नई पंक्ति () {max_base =max_base, base_add =min_int}); min_int =(min_group + 1) * 8192; } } सार्वजनिक स्थैतिक शून्य GetNums_AdamMachanic2_8192_base_fill (ऑब्जेक्ट ओ, लॉन्ग मैक्स_बेस आउट, लॉन्ग बेस_एड आउट) { वर आर =(पंक्ति) ओ; max_base =r.max_base; base_add =r.base_add; }};
मैंने VS प्रोजेक्ट का नाम GetNumsAdam2 रखा और अन्य प्रोजेक्ट्स की तरह पथ C:\Temp\ में रखा। यहाँ वह कोड है जिसका उपयोग मैंने टेस्टडब डेटाबेस में फ़ंक्शन को परिनियोजित करने के लिए किया था:
-- यदि मौजूद है तो असेंबली और फंक्शनड्रॉप फंक्शन बनाएं। .GetNums_AdamMachanic2_8192_base(@max_base AS BIGINT, @add_base AS BIGINT) रिटर्न टेबल (max_base BIGINT, base_add BIGINT) ORDER(base_add) AS बाहरी नाम GetNumsAdam2.1 से 100M की सीमा के साथ GetNums_AdamMachanic2_8192_base का उपयोग करने के लिए यहां एक उदाहरण दिया गया है:
चुनें * dbo से.GetNums_AdamMachanic2_8192_base(1, 100000000);यह कोड निम्नलिखित आउटपुट उत्पन्न करता है, जो यहाँ संक्षिप्त रूप में दिखाया गया है:
मैक्स_बेस बेस_ऐड-------------------------- ------------------------8191 18192 81928192 163848192 245768192 32768...8192 999669768192 999751688192 999833608192 99991552257 99999744(12208 पंक्तियाँ प्रभावित)यहाँ T-SQL फ़ंक्शन GetNums_AdamMachanic2_8192 (संक्षिप्त) की परिभाषा के साथ कोड है:
क्रिएट या अल्टर फंक्शन dbo.GetNums_AdamMachanic2_8192 (@max_base AS BIGINT, @add_base AS BIGINT) रिटर्न टेबल टॉप (@max_base) V.i + @add_base AS val FROM ( VALUES (0), (1), (2), (3), (4), ... (8187), (8188), (8189), (8190), (8191) एएस वी(आई);GOमहत्वपूर्ण: यहां भी, मुझे इस बात पर जोर देना चाहिए कि जैसा मैंने कामिल के दूसरे समाधान के बारे में कहा था, एडम यहां एक धारणा बनाता है कि TOP फ़िल्टर तालिका-मूल्य निर्माता में पंक्ति उपस्थिति क्रम के आधार पर शीर्ष पंक्तियों को निकालेगा, जिसकी वास्तव में गारंटी नहीं है। यदि आप TOP का समर्थन करने के लिए ORDER BY क्लॉज जोड़ते हैं या फ़िल्टर को WHERE फ़िल्टर में बदलते हैं, तो आपको एक नियतात्मक फ़िल्टर मिलेगा, लेकिन यह समाधान के प्रदर्शन प्रोफ़ाइल को पूरी तरह से बदल सकता है।
अंत में, यहां सबसे बाहरी टी-एसक्यूएल फ़ंक्शन है, dbo.GetNums_AdamMachanic2, जिसे अंतिम उपयोगकर्ता नंबर श्रृंखला प्राप्त करने के लिए कॉल करता है:
क्रिएट या अल्टर फंक्शन dbo.GetNums_AdamMachanic2(@low AS BIGINT =1, @high AS BIGINT) रिटर्न टेबल्स रिटर्न सेलेक्ट Y.val AS n FROM ( SELECT max_base, base_add FROM dbo.GetNums_AdamMachanic2_8192) AS X क्रॉस अप्लाई dbo.GetNums_AdamMachanic2_8192(X.max_base, X.base_add) AS YGOयह फ़ंक्शन आंतरिक T-SQL फ़ंक्शन dbo को लागू करने के लिए CROSS APPLY ऑपरेटर का उपयोग करता है। आंतरिक CLR फ़ंक्शन dbo द्वारा लौटाए गए प्रति पंक्ति GetNums_AdamMachanic2_8192। GetNums_AdamMachanic2_8192_base।
आइए पहले समग्र तकनीक का उपयोग करके इस समाधान का परीक्षण करें जब आदेश कोई मायने नहीं रखता:
dbo से MAX(n) AS mx चुनें।GetNums_AdamMachanic2(1, 100000000);मुझे इस निष्पादन के लिए चित्र 7 में दिखाया गया प्लान मिला है।
चित्र 7:dbo.GetNums_AdamMachanic2 फ़ंक्शन के लिए योजना
मुझे इस परीक्षण के लिए निम्नलिखित समय आँकड़े मिले हैं:
SQL सर्वर समय को पार्स और संकलित करें :CPU समय =313 ms, बीता हुआ समय =339 ms .
SQL सर्वर निष्पादन समय :CPU समय =8859 ms, बीता हुआ समय =8849 ms .किसी तार्किक पढ़ने की आवश्यकता नहीं थी।
निष्पादन समय खराब नहीं है, लेकिन बड़े टेबल-वैल्यू कंस्ट्रक्टर के उपयोग के कारण उच्च संकलन समय पर ध्यान दें। आपके द्वारा अनुरोधित सीमा आकार के बावजूद आप इतने उच्च संकलन समय का भुगतान करेंगे, इसलिए बहुत छोटी श्रेणियों के साथ फ़ंक्शन का उपयोग करते समय यह विशेष रूप से मुश्किल है। और यह समाधान अभी भी टी-एसक्यूएल वाले की तुलना में धीमा है।
आइए क्रम के साथ फ़ंक्शन का परीक्षण करें:
घोषित @n के रूप में BIGINT; dbo से @n =n चुनें। GetNums_AdamMachanic2(1, 100000000) n द्वारा ऑर्डर करें;मुझे इस निष्पादन के लिए चित्र 8 में दिखाया गया प्लान मिला है।
चित्र 8:ऑर्डर के साथ dbo.GetNums_AdamMachanic2 फ़ंक्शन के लिए योजना
कामिल के दूसरे समाधान की तरह, योजना में एक स्पष्ट प्रकार की आवश्यकता होती है, जिसमें एक महत्वपूर्ण प्रदर्शन जुर्माना होता है। इस परीक्षण के लिए मुझे मिले समय के आंकड़े यहां दिए गए हैं:
निष्पादन समय:CPU समय =54891 ms, बीता हुआ समय =60981 ms .साथ ही, एक सेकंड के लगभग एक तिहाई का उच्च संकलन समय दंड अभी भी है।
निष्कर्ष
संख्या श्रृंखला चुनौती के लिए सीएलआर-आधारित समाधानों का परीक्षण करना दिलचस्प था क्योंकि कई लोगों ने शुरू में यह मान लिया था कि सबसे अच्छा प्रदर्शन करने वाला समाधान सीएलआर-आधारित होगा। कामिल और एडम ने समान दृष्टिकोण का उपयोग किया, पहले प्रयास में एक साधारण लूप का उपयोग किया जो एक काउंटर को बढ़ाता है और प्रति पुनरावृत्ति अगले मूल्य के साथ एक पंक्ति उत्पन्न करता है, और अधिक परिष्कृत दूसरा प्रयास जो सीएलआर और टी-एसक्यूएल भागों को जोड़ता है। व्यक्तिगत रूप से, मैं इस तथ्य से सहज महसूस नहीं करता कि कामिल और एडम दोनों के दूसरे समाधानों में वे एक गैर-नियतात्मक टॉप फ़िल्टर पर निर्भर थे, और जब मैंने इसे अपने स्वयं के परीक्षण में एक नियतात्मक में परिवर्तित किया, तो इसका समाधान के प्रदर्शन पर प्रतिकूल प्रभाव पड़ा। . किसी भी तरह से, हमारे दो टी-एसक्यूएल समाधान सीएलआर वाले की तुलना में बेहतर प्रदर्शन करते हैं, और जब आपको ऑर्डर की गई पंक्तियों की आवश्यकता होती है तो योजना में स्पष्ट छँटाई नहीं होती है। इसलिए मैं वास्तव में सीएलआर मार्ग को आगे बढ़ाने में कोई मूल्य नहीं देखता। चित्र 9 में मेरे द्वारा इस आलेख में प्रस्तुत किए गए समाधानों का प्रदर्शन सारांश है।
चित्र 9:समय प्रदर्शन तुलना
मेरे लिए, GetNums_AlanCharlieItzikBatch पसंद का समाधान होना चाहिए जब आपको बिल्कुल I/O पदचिह्न की आवश्यकता नहीं होती है, और GetNums_SQKWiki को प्राथमिकता दी जानी चाहिए जब आपको एक छोटा I/O पदचिह्न न हो। बेशक, हम हमेशा उम्मीद कर सकते हैं कि एक दिन Microsoft इस गंभीर रूप से उपयोगी टूल को बिल्ट-इन के रूप में जोड़ देगा, और उम्मीद है कि यदि/जब वे ऐसा करते हैं, तो यह एक प्रदर्शनकारी समाधान होगा जो बैच प्रोसेसिंग और समांतरता का समर्थन करता है। तो इस सुविधा सुधार अनुरोध के लिए वोट करना न भूलें, और शायद अपनी टिप्पणियां भी जोड़ें कि यह आपके लिए क्यों महत्वपूर्ण है।
मुझे इस सीरीज में काम करने में बहुत मजा आया। मैंने इस प्रक्रिया के दौरान बहुत कुछ सीखा, और आशा करता हूं कि आपने भी किया।