कई लोगों के साथ SQL क्वेरी को विभाजित करने में मदद मिलती है?


18

हमें अपने SQL Server 2008 R2 पर हर रात कुछ रिपोर्टिंग करने की आवश्यकता है। रिपोर्ट की गणना में कई घंटे लगते हैं। समय को छोटा करने के लिए हम एक तालिका को प्राथमिकता देते हैं। यह तालिका JOINining 12 के आधार पर बनाई गई है जो काफी बड़ी (दसियों मिलें पंक्ति) टेबल हैं।

इस एकत्रीकरण तालिका की गणना कुछ दिनों पहले cca 4 घंटे तक हुई थी। हमारे डीबीए ने इस बड़े विभाजन को 3 छोटे जोड़ (प्रत्येक 4 तालिकाओं में शामिल) में विभाजित किया। अस्थायी परिणाम को हर बार एक अस्थायी तालिका में सहेजा जाता है, जिसका उपयोग अगली जॉइन में किया जाता है।

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

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

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

तो, प्रश्न हैं:

  1. संभवतः बड़े सुधार का कारण क्या हो सकता है?

  2. क्या यह एक मानक प्रक्रिया है कि बड़े जॉइन को छोटे में विभाजित किया जाए?

  3. क्या डेटा की मात्रा है जो सर्वर कई छोटे जुड़ने के मामले में वास्तव में छोटा है?

यहाँ मूल प्रश्न है:

    Insert Into FinalResult_Base
SELECT       
    TC.TestCampaignContainerId,
    TC.CategoryId As TestCampaignCategoryId,
    TC.Grade,
    TC.TestCampaignId,    
    T.TestSetId
    ,TL.TestId
    ,TSK.CategoryId
    ,TT.[TestletId]
    ,TL.SectionNo
    ,TL.Difficulty
    ,TestletName = Char(65+TL.SectionNo) + CONVERT(varchar(4),6 - TL.Difficulty) 
    ,TQ.[QuestionId]
    ,TS.StudentId
    ,TS.ClassId
    ,RA.SubjectId
    ,TQ.[QuestionPoints] 
    ,GoodAnswer  = Case When TQ.[QuestionPoints] Is null Then 0
                      When TQ.[QuestionPoints] > 0 Then 1 
                      Else 0 End
    ,WrongAnswer = Case When TQ.[QuestionPoints] = 0 Then 1 
                      When TQ.[QuestionPoints] Is null Then 1
                     Else 0 End
    ,NoAnswer    = Case When TQ.[QuestionPoints] Is null Then 1 Else 0 End
    ,TS.Redizo
    ,TT.ViewCount
    ,TT.SpentTime
    ,TQ.[Position]  
    ,RA.SpecialNeeds        
    ,[Version] = 1 
    ,TestAdaptationId = TA.Id
    ,TaskId = TSK.TaskId
    ,TaskPosition = TT.Position
    ,QuestionRate = Q.Rate
    ,TestQuestionId = TQ.Guid
    ,AnswerType = TT.TestletAnswerTypeId
FROM 
    [TestQuestion] TQ WITH (NOLOCK)
    Join [TestTask] TT WITH (NOLOCK)            On TT.Guid = TQ.TestTaskId
    Join [Question] Q WITH (NOLOCK)         On TQ.QuestionId =  Q.QuestionId
    Join [Testlet] TL WITH (NOLOCK)         On TT.TestletId  = TL.Guid 
    Join [Test]     T WITH (NOLOCK)         On TL.TestId     =  T.Guid
    Join [TestSet] TS WITH (NOLOCK)         On T.TestSetId   = TS.Guid 
    Join [RoleAssignment] RA WITH (NOLOCK)  On TS.StudentId  = RA.PersonId And RA.RoleId = 1
    Join [Task] TSK WITH (NOLOCK)       On TSK.TaskId = TT.TaskId
    Join [Category] C WITH (NOLOCK)     On C.CategoryId = TSK.CategoryId
    Join [TimeWindow] TW WITH (NOLOCK)      On TW.Id = TS.TimeWindowId 
    Join [TestAdaptation] TA WITH (NOLOCK)  On TA.Id = TW.TestAdaptationId
    Join [TestCampaign] TC WITH (NOLOCK)        On TC.TestCampaignId = TA.TestCampaignId 
WHERE
    T.TestTypeId = 1    -- eliminuji ankety 
    And t.ProcessedOn is not null -- ne vsechny, jen dokoncene
    And TL.ShownOn is not null
    And TS.Redizo not in (999999999, 111111119)
END;

डीबीए महान कार्य के बाद नए स्प्लिट्स जुड़ते हैं:

    SELECT       
    TC.TestCampaignContainerId,
    TC.CategoryId As TestCampaignCategoryId,
    TC.Grade,
    TC.TestCampaignId,    
    T.TestSetId
    ,TL.TestId
    ,TL.SectionNo
    ,TL.Difficulty
    ,TestletName = Char(65+TL.SectionNo) + CONVERT(varchar(4),6 - TL.Difficulty) -- prevod na A5, B4, B5 ...
    ,TS.StudentId
    ,TS.ClassId
    ,TS.Redizo
    ,[Version] = 1 -- ? 
    ,TestAdaptationId = TA.Id
    ,TL.Guid AS TLGuid
    ,TS.TimeWindowId
INTO
    [#FinalResult_Base_1]
FROM 
    [TestSet] [TS] WITH (NOLOCK)
    JOIN [Test] [T] WITH (NOLOCK) 
        ON [T].[TestSetId] = [TS].[Guid] AND [TS].[Redizo] NOT IN (999999999, 111111119) AND [T].[TestTypeId] = 1 AND [T].[ProcessedOn] IS NOT NULL
    JOIN [Testlet] [TL] WITH (NOLOCK)
        ON [TL].[TestId] = [T].[Guid] AND [TL].[ShownOn] IS NOT NULL
    JOIN [TimeWindow] [TW] WITH (NOLOCK)
        ON [TW].[Id] = [TS].[TimeWindowId] AND [TW].[IsActive] = 1
    JOIN [TestAdaptation] [TA] WITH (NOLOCK)
        ON [TA].[Id] = [TW].[TestAdaptationId] AND [TA].[IsActive] = 1
    JOIN [TestCampaign] [TC] WITH (NOLOCK)
        ON [TC].[TestCampaignId] = [TA].[TestCampaignId] AND [TC].[IsActive] = 1
    JOIN [TestCampaignContainer] [TCC] WITH (NOLOCK)
        ON [TCC].[TestCampaignContainerId] = [TC].[TestCampaignContainerId] AND [TCC].[IsActive] = 1
    ;

 SELECT       
    FR1.TestCampaignContainerId,
    FR1.TestCampaignCategoryId,
    FR1.Grade,
    FR1.TestCampaignId,    
    FR1.TestSetId
    ,FR1.TestId
    ,TSK.CategoryId AS [TaskCategoryId]
    ,TT.[TestletId]
    ,FR1.SectionNo
    ,FR1.Difficulty
    ,TestletName = Char(65+FR1.SectionNo) + CONVERT(varchar(4),6 - FR1.Difficulty) -- prevod na A5, B4, B5 ...
    ,FR1.StudentId
    ,FR1.ClassId
    ,FR1.Redizo
    ,TT.ViewCount
    ,TT.SpentTime
    ,[Version] = 1 -- ? 
    ,FR1.TestAdaptationId
    ,TaskId = TSK.TaskId
    ,TaskPosition = TT.Position
    ,AnswerType = TT.TestletAnswerTypeId
    ,TT.Guid AS TTGuid

INTO
    [#FinalResult_Base_2]
FROM 
    #FinalResult_Base_1 FR1
    JOIN [TestTask] [TT] WITH (NOLOCK)
        ON [TT].[TestletId] = [FR1].[TLGuid] 
    JOIN [Task] [TSK] WITH (NOLOCK)
        ON [TSK].[TaskId] = [TT].[TaskId] AND [TSK].[IsActive] = 1
    JOIN [Category] [C] WITH (NOLOCK)
        ON [C].[CategoryId] = [TSK].[CategoryId]AND [C].[IsActive] = 1
    ;    

DROP TABLE [#FinalResult_Base_1]

CREATE NONCLUSTERED INDEX [#IX_FR_Student_Class]
ON [dbo].[#FinalResult_Base_2] ([StudentId],[ClassId])
INCLUDE ([TTGuid])

SELECT       
    FR2.TestCampaignContainerId,
    FR2.TestCampaignCategoryId,
    FR2.Grade,
    FR2.TestCampaignId,    
    FR2.TestSetId
    ,FR2.TestId
    ,FR2.[TaskCategoryId]
    ,FR2.[TestletId]
    ,FR2.SectionNo
    ,FR2.Difficulty
    ,FR2.TestletName
    ,TQ.[QuestionId]
    ,FR2.StudentId
    ,FR2.ClassId
    ,RA.SubjectId
    ,TQ.[QuestionPoints] -- 1+ good, 0 wrong, null no answer
    ,GoodAnswer  = Case When TQ.[QuestionPoints] Is null Then 0
                      When TQ.[QuestionPoints] > 0 Then 1 -- cookie
                      Else 0 End
    ,WrongAnswer = Case When TQ.[QuestionPoints] = 0 Then 1 
                      When TQ.[QuestionPoints] Is null Then 1
                     Else 0 End
    ,NoAnswer    = Case When TQ.[QuestionPoints] Is null Then 1 Else 0 End
    ,FR2.Redizo
    ,FR2.ViewCount
    ,FR2.SpentTime
    ,TQ.[Position] AS [QuestionPosition]  
    ,RA.SpecialNeeds -- identifikace SVP        
    ,[Version] = 1 -- ? 
    ,FR2.TestAdaptationId
    ,FR2.TaskId
    ,FR2.TaskPosition
    ,QuestionRate = Q.Rate
    ,TestQuestionId = TQ.Guid
    ,FR2.AnswerType
INTO
    [#FinalResult_Base]
FROM 
    [#FinalResult_Base_2] FR2
    JOIN [TestQuestion] [TQ] WITH (NOLOCK)
        ON [TQ].[TestTaskId] = [FR2].[TTGuid]
    JOIN [Question] [Q] WITH (NOLOCK)
        ON [Q].[QuestionId] = [TQ].[QuestionId] AND [Q].[IsActive] = 1

    JOIN [RoleAssignment] [RA] WITH (NOLOCK)
        ON [RA].[PersonId] = [FR2].[StudentId]
        AND [RA].[ClassId] = [FR2].[ClassId] AND [RA].[IsActive] = 1 AND [RA].[RoleId] = 1

    drop table #FinalResult_Base_2;

    truncate table [dbo].[FinalResult_Base];
    insert into [dbo].[FinalResult_Base] select * from #FinalResult_Base;

    drop table #FinalResult_Base;

3
चेतावनी के एक शब्द के साथ - (NOLOCK) बुराई है - परिणामस्वरूप खराब डेटा वापस आ सकता है। मेरा सुझाव है कि (ROWCOMMITTED) के साथ प्रयास करें।
टॉमटॉम

1
@TomTom क्या आपका मतलब है READCOMMITTED? मैंने पहले कभी ROWCOMMITTED नहीं देखा है।
ypercube y

4
(NOLOCK) के साथ बुराई नहीं है। यह सिर्फ जादू की गोली नहीं है जो लोगों को लगता है कि यह है। एसक्यूएल सर्वर की अधिकांश चीजों और सॉफ्टवेयर विकास की तरह इसमें भी इसकी जगह है।
ज़ेन

2
हां, लेकिन यह देखते हुए कि NOLOCK लॉग में चेतावनी दे सकता है और - अधिक महत्वपूर्ण - गलत डेटा लौटाता है, मैं इसे बुराई मानता हूं। यह बहुत महत्वपूर्ण है कि केवल सारणी ही महत्वपूर्ण है कि क्वेरी चलने के दौरान प्राथमिक कुंजी और चयनित कुंजियों में परिवर्तन न किया जाए। और हां, मैं READCOMMMITED माफ करता हूं।
टॉमटॉम

जवाबों:


11

1 इंटरमीडिएट / लेट जॉइन के लिए बेहतर आंकड़ों के साथ 'सर्च स्पेस' को कम करना।

मुझे 90-टेबल जॉइन (मिकी माउस डिज़ाइन) से निपटना पड़ा है जहाँ क्वेरी प्रोसेसर ने योजना बनाने से भी इनकार कर दिया। 9 तालिकाओं के 10 उप-संयोजनों में इस तरह के जोड़ को तोड़कर, नाटकीय रूप से प्रत्येक जुड़ाव की जटिलता को नीचे लाया गया, जो प्रत्येक अतिरिक्त तालिका के साथ तेजी से बढ़ता है। इसके अलावा क्वेरी ऑप्टिमाइज़र अब उन्हें 10 योजनाओं के रूप में मानता है, (संभावित रूप से) समग्र रूप से अधिक समय बिताना (पॉल व्हाइट में मैट्रिक्स भी हो सकता है!)।

इंटरमीडिएट के रिजल्ट टेबल में अब अपने स्वयं के ताजा आंकड़े होंगे, इस प्रकार एक गहरे पेड़ के आंकड़ों की तुलना में बहुत बेहतर जुड़ते हैं जो जल्दी ही तिरछे हो जाते हैं और जल्द ही साइंस फिक्शन के रूप में समाप्त हो जाते हैं।

साथ ही आप सबसे अधिक चयनात्मक जोड़ को पहले मजबूर कर सकते हैं, जिससे पेड़ के ऊपर जाने वाले डेटा वॉल्यूम में कटौती हो सकती है। यदि आप ऑप्टिमाइज़र की तुलना में अपनी भविष्यवाणी की चयनात्मकता का अनुमान लगा सकते हैं, तो ज्वाइन ऑर्डर को बाध्य क्यों न करें। "बुश प्लान" की खोज करने लायक हो सकता है।

2 यह मेरे विचार में माना जाना चाहिए , अगर दक्षता और प्रदर्शन महत्वपूर्ण हैं

3 जरूरी नहीं है, लेकिन यह हो सकता है कि सबसे चयनात्मक जोड़ जल्दी से निष्पादित हो


3
+1 धन्यवाद। विशेष रूप से अपने अनुभव के वर्णन के लिए। यह कहने में बहुत सही है "यदि आप ऑप्टिमाइज़र की तुलना में अपनी भविष्यवाणी की चयनात्मकता का बेहतर अनुमान लगा सकते हैं, तो ज्वाइनिंग ऑर्डर क्यों नहीं।"
ओन्ड्रेज पीटरका

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

6
  1. SQLServer अनुकूलक आमतौर पर एक अच्छा काम करता है। हालांकि, इसका लक्ष्य सबसे अच्छा संभव योजना उत्पन्न करना नहीं है, बल्कि उस योजना को ढूंढना है जो जल्दी से अच्छा है। कई जोड़ों के साथ एक विशेष क्वेरी के लिए यह बहुत खराब प्रदर्शन का कारण हो सकता है। ऐसे मामले का अच्छा संकेत वास्तविक निष्पादन योजना में अनुमानित और वास्तविक पंक्तियों की संख्या के बीच एक बड़ा अंतर है। इसके अलावा, मुझे पूरा यकीन है कि प्रारंभिक क्वेरी के लिए निष्पादन योजना कई 'नेस्टेड लूप्स ज्वाइन' करेगी जो 'मर्ज जॉइन' की तुलना में धीमी है। उत्तरार्द्ध के लिए दोनों इनपुटों को एक ही कुंजी का उपयोग करके सॉर्ट करने की आवश्यकता होती है, जो महंगा है, और आमतौर पर ऑप्टिमाइज़र ऐसे विकल्प को छोड़ देता है। अस्थायी तालिका में परिणाम संग्रहीत करना और उचित अनुक्रमित जोड़ना जैसा कि आपने परिणाम किया था-मुझे लगता है कि आगे के जोड़ों के लिए बेहतर एल्गोरिदम चुनने में (साइड नोट - आप पहले अस्थायी तालिका को आबाद करके सर्वोत्तम प्रथाओं का पालन करते हैं, और बाद में अनुक्रमणिका जोड़ना)। इसके अलावा, SQLServer अस्थायी तालिकाओं के लिए आंकड़े उत्पन्न करता है और रखता है जो उचित सूचकांक चुनने में मदद करता है।
  2. मैं नहीं कह सकता कि अस्थायी तालिकाओं का उपयोग करने के बारे में एक मानक है जब जुड़ने की संख्या कुछ निश्चित संख्या से अधिक है, लेकिन यह निश्चित रूप से एक विकल्प है जो प्रदर्शन में सुधार कर सकता है। ऐसा अक्सर नहीं होता है, लेकिन मुझे कुछ ऐसी ही समस्याएं (और इसी तरह के समाधान) दो बार हुई थीं। वैकल्पिक रूप से, आप अपने आप को सर्वश्रेष्ठ निष्पादन योजना का पता लगाने की कोशिश कर सकते हैं, स्टोर कर सकते हैं और इसे फिर से उपयोग करने के लिए मजबूर कर सकते हैं, लेकिन इसमें बहुत अधिक समय लगेगा (कोई 100% गारंटी नहीं है कि आप सफल होंगे)। एक और पक्ष नोट - अगर अस्थायी तालिका में संग्रहीत परिणामी परिणाम अपेक्षाकृत छोटा है (10k रिकॉर्ड के बारे में) तो तालिका चर टेम्पोरल तालिका से बेहतर प्रदर्शन करता है।
  3. मुझे यह कहते हुए नफरत है कि 'निर्भर करता है', लेकिन यह शायद आपके तीसरे सवाल का जवाब है। ऑप्टिमाइज़र को तेजी से परिणाम देना है; आप यह नहीं चाहते हैं कि यह सबसे अच्छी योजना का पता लगाने के लिए घंटों बिताए; प्रत्येक जुड़ाव अतिरिक्त कार्य जोड़ता है, और कभी-कभी ऑप्टिमाइज़र 'भ्रमित हो जाता है'।

3
पुष्टि और स्पष्टीकरण के लिए +1 धन्यवाद। आपने जो लिखा है वह समझ में आता है।
ओन्ड्रेज पीटरका

4

ठीक है, मुझे यह कहकर शुरू करें कि आप छोटे डेटा पर काम करते हैं - लाखों में से 10 टन बड़े नहीं हैं। पिछले DWH projet मैं 400 मिलियन पंक्तियों को तथ्य तालिका में जोड़ा गया था। हर दिन। 5 साल के लिए भंडारण।

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

अन्यथा, जब तक staitsics क्रम में हैं, क्वेरी ऑप्टिमाइज़र अभिभूत नहीं है। यह वास्तव में परवाह नहीं करता है कि मेज कितनी बड़ी है - यह आंकड़ों द्वारा काम करता है जो वास्तव में विकसित नहीं होते हैं। यह जानकारी: यदि आपके पास वास्तव में एक बड़ी तालिका (पंक्तियों की डबल अंकों की अरब संख्या) है, तो वे थोड़ा मोटे हो सकते हैं।

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

अंत में, हम सीमित हार्डवेयर के साथ काम करते हैं।


1
आपके उत्तर के लिए +1 धन्यवाद। यह कहने में अच्छी बात है कि यह एचडब्ल्यू पर निर्भर करता है। हमारे पास सिर्फ 32 जीबी रैम है, जो शायद पर्याप्त नहीं है।
ओन्ड्रेज पीटरका

2
मैं हर बार जब भी मैं इस तरह से जवाब पढ़ता हूं, मैं थोड़ा निराश हो जाता हूं - यहां तक ​​कि कुछ दर्जन पंक्तियों में हमारे डेटाबेस सर्वर पर घंटों तक सीपीयू लोड होता है। शायद आयामों की संख्या अधिक है, लेकिन 30 आयाम बहुत बड़ी संख्या नहीं है। मुझे लगता है कि आपके द्वारा संसाधित की जा सकने वाली बहुत अधिक संख्या एक साधारण मॉडल से है। इससे भी बदतर: पूरा डेटा रैम में फिट बैठता है। और अभी भी घंटों लगते हैं।
नवंबर को flaschenpost

1
30 आयाम एक बहुत कुछ है - क्या आप सुनिश्चित हैं कि मॉडल एक स्टार में ठीक से अनुकूलित है? उदाहरण के लिए कुछ गलतियाँ जिनकी लागत सीपीयू है - ओपी क्वेरी पर GUID की प्राथमिक कुंजी (अद्वितीय पहचानकर्ता) के रूप में उपयोग की जा रही है। मैं उनसे भी प्यार करता हूं - अद्वितीय सूचकांक के रूप में, प्राथमिक कुंजी एक आईडी फ़ील्ड है, जो पूरी तुलना को तेज़ बनाता है और सूचकांक अधिक nawwox (4 या 8 बाइट्स, 18 नहीं)। इस तरह के ट्रिक्स सीपीयू के एक टन को बचाते हैं।
टॉमटॉम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.