SQL सर्वर में, मुझे निम्नलिखित मामले में एक LOOP JOIN को बाध्य करना चाहिए?


15

आमतौर पर, मैं सभी मानक कारणों के लिए सम्मिलित संकेत का उपयोग करने के खिलाफ सलाह देता हूं। हाल ही में, हालांकि, मुझे एक पैटर्न मिला है जहां मैं लगभग हमेशा बेहतर प्रदर्शन करने के लिए एक मजबूर पाश में शामिल होता हूं। वास्तव में, मैं इसका उपयोग और सिफारिश करने लगा हूं कि मैं यह सुनिश्चित करने के लिए एक दूसरी राय प्राप्त करना चाहता हूं कि मुझे कुछ याद नहीं है। यहां एक प्रतिनिधि परिदृश्य है (उदाहरण के लिए एक विशिष्ट कोड अंत में है):

--Case 1: NO HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID

--Case 2: LOOP JOIN HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID

सैंपलटेबल की 1 मिलियन पंक्तियाँ हैं और इसकी पीके आईडी है।
Temp तालिका #Driver में केवल एक कॉलम, ID, कोई अनुक्रमणिका और 50K पंक्तियाँ नहीं हैं।

मुझे लगातार जो मिल रहा है वह निम्नलिखित है:

केस 1: सैंपलटेबल हैश
पर NO HINT इंडेक्स स्कैन उच्च अवधि (avg 333ms) उच्च CPU (औसत 331ms) लोअर लॉजिकल रीड्स (4714) में शामिल हों



केस 2: सैंपल लूप
पर लोएप ज्वाइंट हंट इंडेक्स लोअर ड्यूरेशन (एवीजी 204ms, 39% कम) लोअर सीपीयू ( एवीजी 206, 38% कम) ज्यादा हायर लॉजिकल रीड्स (160015, 34X अधिक) में शामिल हों।



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

नमूनाटैब 4714 पृष्ठों पर सम्‍मिलित है, जो लगभग 36 एमबी है। केस 1 उन सभी को स्कैन करता है जिसके कारण हमें 4714 रीड मिलते हैं। इसके अलावा, इसे 1 मिलियन हैश करना चाहिए, जो सीपीयू गहन हैं, और जो अंततः समय को आनुपातिक रूप से बढ़ाता है। यह सब हैशिंग है जो केस 1 में समय बढ़ाता है।

अब मामला 2 पर विचार करें। यह कोई हैशिंग नहीं कर रहा है, बल्कि यह 50000 अलग-अलग शोध कर रहा है, जो कि रीड्स को बढ़ा रहा है। लेकिन तुलनात्मक रूप से रीड कितने महंगे हैं? कोई कह सकता है कि अगर वे शारीरिक पढ़े लिखे हैं, तो यह काफी महंगा हो सकता है। लेकिन ध्यान रखें 1) दिए गए पृष्ठ का केवल पहला पाठ भौतिक हो सकता है, और 2) यहां तक ​​कि, केस 1 में भी वही या इससे भी बदतर समस्या होगी क्योंकि यह हर पृष्ठ को हिट करने की गारंटी है।

इस तथ्य के लिए लेखांकन कि दोनों मामलों में कम से कम एक बार प्रत्येक पृष्ठ तक पहुंच है, ऐसा लगता है कि यह एक सवाल है जो तेज है, 1 मिलियन हैश या लगभग 155000 मेमोरी के खिलाफ पढ़ता है? मेरे परीक्षण बाद में कहने लगते हैं, लेकिन SQL सर्वर लगातार पूर्व को चुनता है।

सवाल

तो अपने प्रश्न पर वापस: क्या मुझे इस प्रकार के जॉइंट संकेत को जारी रखना चाहिए जब परीक्षण इस प्रकार के परिणाम दिखाता है, या क्या मैं अपने विश्लेषण में कुछ याद कर रहा हूं? मैं एसक्यूएल सर्वर के ऑप्टिमाइज़र के खिलाफ जाने में संकोच कर रहा हूं, लेकिन ऐसा महसूस होता है कि इन जैसे मामलों में इसे पहले की तुलना में हैश ज्वाइन का उपयोग करने के लिए स्विच करता है।

अपडेट 2014-04-28

मैंने कुछ और परीक्षण किए, और पता चला कि जो परिणाम मैं (एक वीएम डब्ल्यू / 2 सीपीयू पर) प्राप्त कर रहा था, मैं अन्य वातावरण में दोहरा नहीं सकता था (मैंने 8 और 12 सीपीयू के साथ 2 अलग-अलग भौतिक मशीनों पर कोशिश की)। ऑप्टिमाइज़र ने बाद के मामलों में उस बिंदु पर बहुत बेहतर किया, जहां ऐसा कोई स्पष्ट मुद्दा नहीं था। मुझे लगता है कि सबक सीखा, जो पूर्वव्यापी में स्पष्ट लगता है, यह है कि पर्यावरण काफी हद तक प्रभावित कर सकता है कि अनुकूलक कितना अच्छा काम करता है।

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

निष्पादन योजना प्रकरण 1 योजना 1 निष्पादन योजना प्रकरण 2 यहाँ छवि विवरण दर्ज करें

नमूना मामला उत्पन्न करने के लिए कोड

------------------------------------------------------------
-- 1. Create SampleTable with 1,000,000 rows
------------------------------------------------------------    

CREATE TABLE SampleTable
    (  
       ID         INT NOT NULL PRIMARY KEY CLUSTERED
     , Number1    INT NOT NULL
     , Number2    INT NOT NULL
     , Number3    INT NOT NULL
     , Number4    INT NOT NULL
     , Number5    INT NOT NULL
    )

--Add 1 million rows
;WITH  
    Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows  
    Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows 
    Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows 
    Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows 
    Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows 
    FinalCte AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Cte5)
INSERT INTO SampleTable
SELECT Number, Number, Number, Number, Number, Number
FROM  FinalCte
WHERE Number <= 1000000

------------------------------------------------------------
-- Create 2 SPs that join from #Driver to SampleTable.
------------------------------------------------------------    
GO
IF OBJECT_ID('JoinTest_NoHint') IS NOT NULL DROP PROCEDURE JoinTest_NoHint
GO
CREATE PROC JoinTest_NoHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    JOIN SampleTable AS S ON S.ID = D.ID
GO
IF OBJECT_ID('JoinTest_LoopHint') IS NOT NULL DROP PROCEDURE JoinTest_LoopHint
GO
CREATE PROC JoinTest_LoopHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
GO

------------------------------------------------------------
-- Create driver table with 50K rows
------------------------------------------------------------    
GO
IF OBJECT_ID('tempdb..#Driver') IS NOT NULL DROP TABLE #Driver
SELECT ID
INTO #Driver
FROM SampleTable
WHERE ID % 20 = 0

------------------------------------------------------------
-- Run each test and run Profiler
------------------------------------------------------------    

GO
/*Reg*/  EXEC JoinTest_NoHint
GO
/*Loop*/ EXEC JoinTest_LoopHint


------------------------------------------------------------
-- Results
------------------------------------------------------------    

/*

Duration CPU   Reads    TextData
315      313   4714     /*Reg*/  EXEC JoinTest_NoHint
309      296   4713     /*Reg*/  EXEC JoinTest_NoHint
327      329   4713     /*Reg*/  EXEC JoinTest_NoHint
398      406   4715     /*Reg*/  EXEC JoinTest_NoHint
316      312   4714     /*Reg*/  EXEC JoinTest_NoHint
217      219   160017   /*Loop*/ EXEC JoinTest_LoopHint
211      219   160014   /*Loop*/ EXEC JoinTest_LoopHint
217      219   160013   /*Loop*/ EXEC JoinTest_LoopHint
190      188   160013   /*Loop*/ EXEC JoinTest_LoopHint
187      187   160015   /*Loop*/ EXEC JoinTest_LoopHint

*/

जवाबों:


13

नमूनाटैब 4714 पृष्ठों पर सम्‍मिलित है, जो लगभग 36 एमबी है। केस 1 उन सभी को स्कैन करता है जिसके कारण हमें 4714 रीड मिलते हैं। इसके अलावा, इसे 1 मिलियन हैश करना चाहिए, जो सीपीयू गहन हैं, और जो अंततः समय को आनुपातिक रूप से बढ़ाता है। यह सब हैशिंग है जो केस 1 में समय बढ़ाता है।

हैश ज्वाइन (स्टार्टिंग टेबल बनाना, जो एक ब्लॉकिंग ऑपरेशन भी है) के लिए एक स्टार्ट-अप लागत है, लेकिन हैश ज्वाइन में अंततः SQL सर्वर द्वारा समर्थित तीन भौतिक जॉइन प्रकारों की न्यूनतम सैद्धांतिक प्रति-पंक्ति लागत है, दोनों में IO और CPU की शर्तें। हैश ज्वाइन वास्तव में अपने आप में एक अपेक्षाकृत छोटे बिल्ड इनपुट और एक बड़े जांच इनपुट के साथ आता है। उस ने कहा, सभी परिदृश्यों में कोई भी भौतिक प्रकार 'बेहतर' नहीं है।

अब मामला 2 पर विचार करें। यह कोई हैशिंग नहीं कर रहा है, बल्कि यह 50000 अलग-अलग शोध कर रहा है, जो कि रीड्स को बढ़ा रहा है। लेकिन तुलनात्मक रूप से कितने महंगे हैं? कोई कह सकता है कि अगर वे शारीरिक पढ़े लिखे हैं, तो यह काफी महंगा हो सकता है। लेकिन ध्यान रखें 1) दिए गए पृष्ठ का केवल पहला पाठ भौतिक हो सकता है, और 2) यहां तक ​​कि, केस 1 में भी वही या इससे भी बदतर समस्या होगी क्योंकि यह हर पृष्ठ को हिट करने की गारंटी है।

प्रत्येक चाहने वाले को एक बी-ट्री को जड़ से नेविगेट करने की आवश्यकता होती है, जो कि एकल हैश जांच के साथ तुलनात्मक रूप से महंगा है। इसके अलावा, एक नेस्टेड लूप्स के अंदरूनी हिस्से के लिए सामान्य IO पैटर्न यादृच्छिक है, जो कि जांच-साइड स्कैन इनपुट के अनुक्रमिक एक्सेस पैटर्न के साथ हैश जॉइन करता है। अंतर्निहित भौतिक IO सबसिस्टम के आधार पर, क्रमिक रीड्स यादृच्छिक रीड्स की तुलना में तेज हो सकते हैं। इसके अलावा, SQL सर्वर रीड-फॉरवर्ड मैकेनिज्म अनुक्रमिक IO के साथ बेहतर काम करता है, बड़ा रीड जारी करता है।

इस तथ्य के लिए लेखांकन कि दोनों मामलों में कम से कम एक बार प्रत्येक पृष्ठ तक पहुंच है, ऐसा लगता है कि यह एक सवाल है जो तेज है, 1 मिलियन हैश या लगभग 155000 मेमोरी के खिलाफ पढ़ता है? मेरे परीक्षण बाद में कहने लगते हैं, लेकिन SQL सर्वर लगातार पूर्व को चुनता है।

SQL सर्वर क्वेरी ऑप्टिमाइज़र कई मान्यताओं को बनाता है। एक यह है कि किसी प्रश्न द्वारा किए गए पृष्ठ पर पहली पहुँच के परिणामस्वरूप एक भौतिक IO ('कोल्ड कैश अनुमान') होगा। मौका है कि बाद में पढ़ा जाने वाला एक पेज पहले से ही स्मृति में पढ़े गए पेज से आएगा, जो एक ही मॉडल द्वारा तैयार किया गया है, लेकिन यह एक शिक्षित अनुमान से अधिक नहीं है।

ऑप्टिमाइज़र के मॉडल के इस तरह से काम करने का कारण यह है कि आमतौर पर सबसे खराब स्थिति के लिए अनुकूलित करना बेहतर होता है (शारीरिक IO की आवश्यकता होती है)। कई कमियों को समानता और स्मृति में चल रही चीजों द्वारा कवर किया जा सकता है। यदि यह अनुमान अवैध साबित हो जाता है, तो यह सुनिश्चित करने के लिए कि सभी डेटा स्मृति में खराब था, तो क्वेरी प्लान ऑप्टिमाइज़र का उत्पादन करेगा।

कोल्ड कैश धारणा का उपयोग करते हुए उत्पादित योजना काफी अच्छा प्रदर्शन नहीं कर सकती है यदि इसके बजाय एक गर्म कैश माना जाता है, लेकिन इसकी सबसे खराब स्थिति आमतौर पर बेहतर होगी।

जब मैं इस प्रकार के परिणामों को दिखाता हूं, तो क्या मुझे इस लूप जॉइंट संकेत को जारी रखना चाहिए या क्या मैं अपने विश्लेषण में कुछ याद कर रहा हूं? मैं एसक्यूएल सर्वर के ऑप्टिमाइज़र के खिलाफ जाने में संकोच कर रहा हूं, लेकिन ऐसा महसूस होता है कि इन जैसे मामलों में इसे पहले की तुलना में हैश ज्वाइन का उपयोग करने के लिए स्विच करता है।

आपको दो कारणों से ऐसा करने में बहुत सावधानी बरतनी चाहिए। सबसे पहले, संकेतों में शामिल हों, चुपचाप क्वेरी के लिखित क्रम से मेल खाने के लिए भौतिक जॉइन ऑर्डर को बाध्य करें (जैसे कि आपने भी निर्दिष्ट किया था OPTION (FORCE ORDER)। यह ऑप्टिमाइज़र के लिए उपलब्ध विकल्पों को गंभीर रूप से सीमित करता है, और हमेशा ऐसा नहीं हो सकता है जो आप चाहते हैं। OPTION (LOOP JOIN)बलों नेस्टेड नेस्टेड नेस्टेड । क्वेरी के लिए जुड़ता है, लेकिन लिखित जॉइन ऑर्डर को लागू नहीं करता है।

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

कुल मिलाकर, जब तक कि लूप जॉन्स को मजबूर करने के लिए एक सम्मोहक कारण नहीं होगा, मैं इससे बचूंगा। डिफ़ॉल्ट योजनाएं आमतौर पर इष्टतम के काफी करीब होती हैं, और बदलती परिस्थितियों के कारण अधिक लचीला हो जाती हैं।


धन्यवाद पॉल। उत्कृष्ट विस्तृत विश्लेषण। मैंने आगे कुछ परीक्षण के आधार पर, मुझे लगता है कि क्या हो रहा है कि ऑप्टिमाइज़र के शिक्षित अनुमान इस विशेष उदाहरण के लिए लगातार बंद हैं जब अस्थायी तालिका का आकार 5K और 100K के बीच है। इस तथ्य को देखते हुए कि हमारी आवश्यकताएं गारंटी देती हैं अस्थायी तालिका <50K होगी, यह मेरे लिए सुरक्षित लगता है। मैं उत्सुक हूं, क्या आप अभी भी इसे जानने के किसी भी प्रकार के संकेत से बचेंगे?
जॉनीएम

1
@ जॉननी संकेत एक कारण से मौजूद हैं। उनका उपयोग करना ठीक है जहां आपके पास ऐसा करने के लिए ध्वनि कारण हैं। जिसके अनुसार, मैं शायद ही कभी का उपयोग शामिल हो क्योंकि निहित के संकेत FORCE ORDER। विषम अवसर पर मैं एक जॉइंट संकेत का उपयोग करता हूं, मैं अक्सर OPTION (FORCE ORDER)यह समझाने के लिए एक टिप्पणी के साथ जोड़ता हूं कि क्यों।
पॉल व्हाइट 9

0

एक लाख-पंक्ति तालिका के विरुद्ध सम्मिलित 50,000 पंक्तियाँ सूचकांक के बिना किसी भी तालिका के लिए बहुत कुछ लगती हैं।

आपको इस मामले में वास्तव में क्या करना है, यह बताना मुश्किल है, क्योंकि यह उस समस्या से अलग-थलग है जिसे आप वास्तव में हल करने की कोशिश कर रहे हैं। मैं निश्चित रूप से आशा करता हूं कि यह आपके कोड के भीतर एक सामान्य पैटर्न नहीं है जहां आप महत्वपूर्ण मात्रा में पंक्तियों के साथ कई अनइन्डेक्स अस्थायी टेबलों के खिलाफ शामिल हो रहे हैं।

उदाहरण के लिए इसे क्या कहते हैं, सिर्फ # सूचकांक पर एक सूचकांक क्यों नहीं रखा? क्या डीआईडी ​​वास्तव में अद्वितीय है? यदि ऐसा है, तो यह शब्द EXISTS कथन के समतुल्य है, जो कम से कम SQL सर्वर को यह बताएगा कि आप D के डुप्लिकेट मानों के लिए S खोज जारी रखना नहीं चाहते हैं:

SELECT S.*
INTO #Results
FROM SampleTable S
WHERE EXISTS (SELECT * #Driver D WHERE S.ID = D.ID);

संक्षेप में, इस पैटर्न के लिए, मैं एक LOOP संकेत का उपयोग नहीं करेगा। मैं बस इस पैटर्न का उपयोग नहीं करूंगा। मैं निम्नलिखित में से एक करूँगा, प्राथमिकता के क्रम में यदि संभव हो तो:

  • यदि संभव हो तो #Driver के लिए एक अस्थायी तालिका के बजाय CTE का उपयोग करें
  • # आईडी पर #Driver पर एक यूनिक नॉनक्लेस्टेड इंडेक्स का उपयोग करें यदि यह अद्वितीय है (यह मानते हुए कि आप #Driver का उपयोग करने का एकमात्र समय है और आप टेबल से कोई डेटा नहीं चाहते हैं - यदि आपको वास्तव में उस टेबल से डेटा की आवश्यकता है, तो आप इसे क्लस्टर इंडेक्स बनाने के लिए अच्छा हो सकता है)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.