सबक्वेरी के साथ बड़ी टेबल पर धीमा अपडेट


16

साथ SourceTable> होने 15MM रिकॉर्ड और Bad_Phraseहोने> 3K रिकॉर्ड, निम्न क्वेरी SQL सर्वर 2005 SP4 पर चलाने के लिए लगभग 10 घंटे लगते हैं।

UPDATE [SourceTable] 
SET 
    Bad_Count=
             (
               SELECT 
                  COUNT(*) 
               FROM Bad_Phrase 
               WHERE 
                  [SourceTable].Name like '%'+Bad_Phrase.PHRASE+'%'
             )

अंग्रेजी में, यह क्वेरी Bad_Phrase में सूचीबद्ध अलग-अलग वाक्यांशों की संख्या की गिनती कर रही है जो क्षेत्र Nameमें एक विकल्प हैं SourceTableऔर फिर उस परिणाम को फ़ील्ड में रखते हैं Bad_Count

मैं कुछ सुझाव देना चाहूंगा कि इस क्वेरी को कैसे अधिक तेज़ी से चलाया जाए।


3
तो आप तालिका को 3K बार स्कैन कर रहे हैं, और संभावित रूप से सभी 3K समय में सभी 15MM पंक्तियों को अपडेट कर रहे हैं, और आप इसे तेज़ होने की उम्मीद करते हैं?
हारून बर्ट्रेंड

1
नाम स्तंभ की लंबाई क्या है? क्या आप एक स्क्रिप्ट या एसक्यूएल फिडेल पोस्ट कर सकते हैं जो परीक्षण डेटा उत्पन्न करता है और इस तरह से धीमी गति से क्वेरी को पुन: उत्पन्न करता है जिसमें हम में से कोई भी खेल सकता है? शायद मैं सिर्फ एक आशावादी व्यक्ति हूं, लेकिन मुझे लगता है कि हम 10 घंटे से ज्यादा बेहतर कर सकते हैं। मैं अन्य टिप्पणीकारों से सहमत हूं कि यह एक कम्प्यूटेशनल रूप से महंगी समस्या है, लेकिन मैं यह नहीं देखता कि हम अभी भी इसे "काफी तेजी से" बनाने का लक्ष्य क्यों नहीं बना सकते हैं।
ज्यॉफ पैटरसन

3
मैथ्यू, क्या आपने पूर्ण पाठ अनुक्रमण माना है? आप CONTAINS जैसी चीजों का उपयोग कर सकते हैं और फिर भी उस खोज के लिए अनुक्रमण का लाभ प्राप्त कर सकते हैं।
स्वैसेक

इस मामले में मैं सुझाव देना चाहूंगा कि पंक्ति-आधारित तर्क (यानी 15MM पंक्तियों के 1 अद्यतन के बजाय SourceTable में प्रत्येक पंक्ति को 15MM अपडेट करें, या कुछ अपेक्षाकृत छोटे विखंडूओं को अपडेट करें)। कुल समय तेज नहीं होने वाला है (भले ही यह इस विशेष मामले में संभव हो), लेकिन इस तरह के दृष्टिकोण से बाकी सिस्टम बिना किसी रुकावट के काम करना जारी रखता है, इससे आपको लेन-देन लॉग आकार पर नियंत्रण मिलता है (जैसे कि हर 10k अपडेट को कमिट करें), बीच में सभी पिछले अपडेट को खोए बिना किसी भी समय अपडेट ...
a1ex07

2
@swasheck पूर्ण-पाठ विचार करने के लिए एक अच्छा विचार है (यह 2005 में नया है, मुझे विश्वास है, इसलिए यहां लागू हो सकता है), लेकिन पूर्ण-पाठ अनुक्रमित शब्दों के बाद से पोस्टर के लिए पूछी गई समान कार्यक्षमता प्रदान करना संभव नहीं होगा और नहीं मनमाना उपजाऊ। दूसरे तरीके से कहा, पूर्ण-पाठ को "शानदार" शब्द के भीतर "चींटी" के लिए एक मैच नहीं मिलेगा। लेकिन यह संभव हो सकता है कि व्यावसायिक आवश्यकताओं को संशोधित किया जा सके ताकि पूर्ण-पाठ लागू हो जाए।
ज्यॉफ पैटरसन

जवाबों:


21

जबकि मैं अन्य टिप्पणीकारों से सहमत हूं कि यह एक कम्प्यूटेशनल रूप से महंगी समस्या है, मुझे लगता है कि आपके द्वारा उपयोग किए जा रहे एसक्यूएल को ट्वीक करके सुधार के लिए बहुत जगह है। वर्णन करने के लिए, मैं 15MM नामों और 3K वाक्यांशों के साथ एक नकली डेटा सेट बनाता हूं, पुराने दृष्टिकोण को चलाया, और एक नया दृष्टिकोण चलाया।

एक नकली डेटा सेट उत्पन्न करने के लिए पूरी स्क्रिप्ट और नए दृष्टिकोण की कोशिश करें

टी एल; डॉ

मेरी मशीन और इस नकली डेटा सेट पर, मूल दृष्टिकोण को चलने में लगभग 4 घंटे लगते हैं। प्रस्तावित नए दृष्टिकोण में लगभग 10 मिनट लगते हैं , काफी सुधार हुआ है। यहाँ प्रस्तावित दृष्टिकोण का एक संक्षिप्त सारांश है:

  • प्रत्येक नाम के लिए, प्रत्येक वर्ण ऑफसेट पर शुरू होने वाली सबस्ट्रिंग उत्पन्न करें (और अनुकूलन के रूप में सबसे लंबे समय तक खराब वाक्यांश की लंबाई पर छाया हुआ)
  • इन सबस्ट्रिंग पर क्लस्टर इंडेक्स बनाएं
  • प्रत्येक बुरे वाक्यांश के लिए, किसी भी मैच की पहचान करने के लिए इन सबस्ट्रिंग में तलाश करें
  • प्रत्येक मूल स्ट्रिंग के लिए, उस स्ट्रिंग के एक या अधिक सबस्ट्रिंग से मेल खाने वाले विभिन्न बुरे वाक्यांशों की संख्या की गणना करें


मूल दृष्टिकोण: एल्गोरिथम विश्लेषण

मूल UPDATEकथन की योजना से , हम देख सकते हैं कि काम की राशि दोनों नामों की संख्या (15 मिमी) और वाक्यांशों की संख्या (3K) दोनों के लिए आनुपातिक रूप से आनुपातिक है। इसलिए यदि हम नामों और वाक्यांशों की संख्या 10 से कई गुणा करते हैं, तो कुल रन समय ~ 100 गुना धीमा होने वाला है।

क्वेरी वास्तव में की लंबाई के समानुपाती है name; जबकि यह क्वेरी प्लान में थोड़ा छिपा हुआ है, यह टेबल स्पूल में मांगने के लिए "निष्पादन की संख्या" में आता है। वास्तविक योजना में, हम यह देख सकते हैं कि यह न केवल एक बार होता है name, बल्कि वास्तव में एक बार प्रति वर्ण ऑफसेट होता है name। तो यह दृष्टिकोण रन-टाइम जटिलता में ओ ( # names* # phrases* name length) है।

यहाँ छवि विवरण दर्ज करें


नया तरीका: कोड

यह कोड पूर्ण pastebin में भी उपलब्ध है लेकिन मैंने इसे सुविधा के लिए यहाँ कॉपी किया है। पास्टबिन की पूरी प्रक्रिया परिभाषा भी है, जिसमें वर्तमान बैच की सीमाओं को परिभाषित करने के लिए नीचे दिए गए चर @minIdऔर @maxIdचर शामिल हैं।

-- For each name, generate the string at each offset
DECLARE @maxBadPhraseLen INT = (SELECT MAX(LEN(phrase)) FROM Bad_Phrase)
SELECT s.id, sub.sub_name
INTO #SubNames
FROM (SELECT * FROM SourceTable WHERE id BETWEEN @minId AND @maxId) s
CROSS APPLY (
    -- Create a row for each substring of the name, starting at each character
    -- offset within that string.  For example, if the name is "abcd", this CROSS APPLY
    -- will generate 4 rows, with values ("abcd"), ("bcd"), ("cd"), and ("d"). In order
    -- for the name to be LIKE the bad phrase, the bad phrase must match the leading X
    -- characters (where X is the length of the bad phrase) of at least one of these
    -- substrings. This can be efficiently computed after indexing the substrings.
    -- As an optimization, we only store @maxBadPhraseLen characters rather than
    -- storing the full remainder of the name from each offset; all other characters are
    -- simply extra space that isn't needed to determine whether a bad phrase matches.
    SELECT TOP(LEN(s.name)) SUBSTRING(s.name, n.n, @maxBadPhraseLen) AS sub_name 
    FROM Numbers n
    ORDER BY n.n
) sub
-- Create an index so that bad phrases can be quickly compared for a match
CREATE CLUSTERED INDEX IX_SubNames ON #SubNames (sub_name)

-- For each name, compute the number of distinct bad phrases that match
-- By "match", we mean that the a substring starting from one or more 
-- character offsets of the overall name starts with the bad phrase
SELECT s.id, COUNT(DISTINCT b.phrase) AS bad_count
INTO #tempBadCounts
FROM dbo.Bad_Phrase b
JOIN #SubNames s
    ON s.sub_name LIKE b.phrase + '%'
GROUP BY s.id

-- Perform the actual update into a "bad_count_new" field
-- For validation, we'll compare bad_count_new with the originally computed bad_count
UPDATE s
SET s.bad_count_new = COALESCE(b.bad_count, 0)
FROM dbo.SourceTable s
LEFT JOIN #tempBadCounts b
    ON b.id = s.id
WHERE s.id BETWEEN @minId AND @maxId


नया तरीका: क्वेरी प्लान

सबसे पहले, हम प्रत्येक वर्ण ऑफसेट पर शुरू होने वाले सबस्ट्रिंग को उत्पन्न करते हैं

यहाँ छवि विवरण दर्ज करें

फिर इन सबस्ट्रिंग पर एक क्लस्टर इंडेक्स बनाएं

यहाँ छवि विवरण दर्ज करें

अब, प्रत्येक बुरे वाक्यांश के लिए हम किसी भी मैच की पहचान करने के लिए इन सबस्ट्रिंग्स की तलाश करते हैं। फिर हम उस स्ट्रिंग के एक या अधिक सबस्ट्रिंग से मेल खाने वाले अलग-अलग बुरे वाक्यांशों की संख्या की गणना करते हैं। यह वास्तव में महत्वपूर्ण कदम है; जिस तरह से हमने सबस्ट्रिंग को अनुक्रमित किया है, उसके कारण, हमें अब खराब वाक्यांशों और नामों के पूर्ण क्रॉस-उत्पाद की जांच नहीं करनी है। यह कदम, जो वास्तविक गणना करता है, वास्तविक रन-टाइम के लगभग 10% के लिए जिम्मेदार है (शेष सबस्ट्रिंग का पूर्व-प्रसंस्करण है)।

यहाँ छवि विवरण दर्ज करें

अंत में, LEFT OUTER JOINकिसी भी नाम के लिए 0 की गिनती निर्दिष्ट करने के लिए वास्तविक अपडेट स्टेटमेंट का उपयोग करें, जिसके लिए हमें कोई भी खराब वाक्यांश नहीं मिला।

यहाँ छवि विवरण दर्ज करें


नया दृष्टिकोण: एल्गोरिथम विश्लेषण

नए दृष्टिकोण को दो चरणों में विभाजित किया जा सकता है, पूर्व-प्रसंस्करण और मिलान। आइए निम्नलिखित चर को परिभाषित करें:

  • N = # नामों का
  • B = # बुरे वाक्यांशों का
  • L = औसत नाम लंबाई, वर्णों में

प्री-प्रोसेसिंग चरण O(N*L * LOG(N*L))बनाने के लिए हैN*L सब्सट्रिंग और फिर उन्हें सॉर्ट करने के लिए है।

वास्तविक मिलान है O(B * LOG(N*L)) प्रत्येक बुरे वाक्यांश के लिए सबस्ट्रिंग में तलाश करने के लिए है।

इस तरह, हमने एक एल्गोरिथ्म बनाया है जो कि खराब वाक्यांशों की संख्या के साथ रैखिक रूप से स्केल नहीं करता है, एक प्रमुख प्रदर्शन अनलॉक होता है, जैसा कि हम 3K वाक्यांशों और उससे परे के पैमाने पर करते हैं। एक और तरीका कहा, मूल कार्यान्वयन में लगभग 10x लगते हैं जब तक हम 300 बुरे वाक्यांशों से 3K बुरे वाक्यांशों पर जाते हैं। इसी तरह जब तक हम 3K खराब वाक्यांशों से 30K तक जाने के लिए एक और 10x लगेंगे। नए कार्यान्वयन, हालांकि, उप-रैखिक रूप से बड़े पैमाने पर होगा और वास्तव में 2x से कम समय लेता है जो 3K खराब वाक्यांशों पर मापा जाता है जब 30K खराब वाक्यांशों तक बढ़ाया जाता है।


मान्यताओं / Caveats

  • मैं समग्र कार्य को मामूली आकार के बैचों में विभाजित कर रहा हूं। यह शायद या तो दृष्टिकोण के लिए एक अच्छा विचार है, लेकिन नए दृष्टिकोण के लिए यह विशेष रूप से महत्वपूर्ण है ताकि SORTसबस्ट्रिंग पर प्रत्येक बैच के लिए स्वतंत्र हो और आसानी से स्मृति में फिट हो। आप आवश्यकतानुसार बैच आकार में हेरफेर कर सकते हैं, लेकिन एक बैच में सभी 15 मिमी पंक्तियों को आज़माना बुद्धिमानी नहीं होगी।
  • मैं SQL 2014 पर हूँ, SQL 2005 नहीं, क्योंकि मेरे पास SQL ​​2005 मशीन नहीं है। मैं SQL 2005 में उपलब्ध किसी भी वाक्यविन्यास का उपयोग नहीं करने के लिए सावधान रहा, लेकिन मुझे अभी भी SQL 2012 में tempdb आलसी लेखन सुविधा और SQL 2014 में समानांतर चयन INTO सुविधा से लाभ मिल सकता है ।
  • दोनों नामों और वाक्यांशों की लंबाई नए दृष्टिकोण के लिए काफी महत्वपूर्ण है। मैं मान रहा हूं कि खराब वाक्यांश आमतौर पर काफी कम होते हैं क्योंकि वास्तविक दुनिया के उपयोग के मामलों से मेल खाने की संभावना है। नाम बुरे वाक्यांशों की तुलना में थोड़ा लंबा है, लेकिन माना जाता है कि यह हजारों वर्ण नहीं हैं। मुझे लगता है कि यह एक उचित धारणा है, और लंबे समय तक नाम के तार आपके मूल दृष्टिकोण को धीमा कर देंगे।
  • सुधार का कुछ हिस्सा (लेकिन कहीं भी यह सभी के करीब है) इस तथ्य के कारण है कि नया दृष्टिकोण पुराने दृष्टिकोण (जो एकल-थ्रेडेड चलाता है) की तुलना में अधिक प्रभावी ढंग से समानता का लाभ उठा सकता है। मैं एक क्वाड कोर लैपटॉप पर हूं, इसलिए यह दृष्टिकोण अच्छा है जो इन कोर को उपयोग करने के लिए रख सकता है।


संबंधित ब्लॉग पोस्ट

हारून बर्ट्रेंड ने अपने ब्लॉग पोस्ट में इस तरह के समाधान की अधिक विस्तार से खोज की एक प्रमुख% वाइल्डकार्ड के लिए एक सूचकांक प्राप्त करने का एक तरीका


6

चलो एक दूसरे के लिए टिप्पणियों में हारून बर्ट्रेंड द्वारा लाए गए स्पष्ट मुद्दे को उठाते हैं :

तो आप तालिका को 3K बार स्कैन कर रहे हैं, और संभावित रूप से सभी 3K समय में सभी 15MM पंक्तियों को अपडेट कर रहे हैं, और आप इसे तेज़ होने की उम्मीद करते हैं?

यह तथ्य कि आपका उपशम दोनों पक्षों पर वाइल्ड कार्ड का उपयोग करता है नाटकीय रूप से व्यंग्यता को प्रभावित करता है । उस ब्लॉग पोस्ट से उद्धरण लेने के लिए:

इसका मतलब है कि SQL सर्वर को उत्पाद तालिका से बाहर हर पंक्ति को पढ़ना है, यह देखने के लिए जांचें कि क्या यह नाम में कहीं भी "अखरोट" मिला है, और फिर हमारे परिणाम लौटाएं।

प्रत्येक "बुरे शब्द" और "उत्पाद" के लिए "अखरोट" शब्द को स्वैप करें SourceTable, फिर हारून की टिप्पणी के साथ संयोजन करें और आपको यह देखना शुरू करना चाहिए कि अपने वर्तमान एल्गोरिथ्म का उपयोग करके इसे जल्दी से चलाने के लिए यह बहुत कठिन (असंभव क्यों है )।

मुझे कुछ विकल्प दिखाई देते हैं:

  1. एक राक्षस सर्वर खरीदने के लिए व्यवसाय को मनाएं, जिसमें इतनी शक्ति हो कि वह कतरनी बल द्वारा क्वेरी पर काबू पा ले। (यह होने वाला नहीं है इसलिए अपनी उंगलियों को पार करें अन्य विकल्प बेहतर हैं)
  2. अपने मौजूदा एल्गोरिदम का उपयोग करते हुए, दर्द को एक बार स्वीकार करें और फिर उसे फैलाएं। इसमें सम्मिलित करने पर बुरे शब्दों की गणना करना शामिल होगा जो आवेषण को धीमा कर देगा, और केवल एक नया बुरा शब्द दर्ज / खोजे जाने पर पूरी तालिका को अपडेट करेगा।
  3. ज्योफ के जवाब को गले लगाओ । यह एक महान एल्गोरिथ्म है, और इससे भी बेहतर है कि मैं इसके साथ आया हूं।
  4. विकल्प 2 करें लेकिन अपने एल्गोरिथ्म को ज्योफ के साथ बदलें।

आपकी आवश्यकताओं के आधार पर मैं या तो विकल्प 3 या 4 की सिफारिश करूंगा।


0

पहले यह सिर्फ एक अजीब अद्यतन है

Update [SourceTable]  
   Set [SourceTable].[Bad_Count] = [fix].[count]
  from [SourceTable] 
  join ( Select count(*) 
           from [Bad_Phrase]  
          where [SourceTable].Name like '%' + [Bad_Phrase].[PHRASE] + '%')

जैसे '%' + [Bad_Phrase]। [PHRASE] आपको मार रहा है
जो इंडेक्स का उपयोग नहीं कर सकता है

डेटा डिज़ाइन गति के लिए इष्टतम नहीं है
क्या आप [Bad_Phrase] को तोड़ सकते हैं। [PHRASE] एक वाक्यांश (शब्द) / शब्द में?
यदि एक ही वाक्यांश / शब्द एक से अधिक दिखाई देता है, तो आप इसे एक से अधिक बार दर्ज कर सकते हैं यदि आप चाहते हैं कि इसकी उच्च गिनती हो,
तो खराब चरण में पंक्तियों की संख्या बढ़ जाएगी
यदि आप कर सकते हैं तो यह बहुत तेजी से होगा

Update [SourceTable]  
   Set [SourceTable].[Bad_Count] = [fix].[count]
  from [SourceTable] 
  join ( select [PHRASE], count(*) as count 
           from [Bad_Phrase] 
          group by [PHRASE] 
       ) as [fix]
    on [fix].[PHRASE] = [SourceTable].[name]  
 where [SourceTable].[Bad_Count] <> [fix].[count]

यह सुनिश्चित नहीं है कि 2005 इसका समर्थन करता है लेकिन पूर्ण पाठ सूचकांक और इसमें शामिल हैं


1
मुझे नहीं लगता कि ओपी खराब शब्द तालिका में बुरे शब्द के उदाहरणों को गिनना चाहता है, मुझे लगता है कि वे स्रोत तालिका में छिपे हुए बुरे शब्दों की संख्या गिनना चाहते हैं। उदाहरण के लिए मूल कोड शायद "शिटास" के नाम के लिए 2 की गिनती देगा, लेकिन आपका कोड 0. की गिनती देगा
एरिक

1
@ एरिक "क्या आप [Bad_Phrase] को तोड़ सकते हैं। [PHRASE] अप टू सिंगल वाक्यांश (शब्द)?" वास्तव में आपको नहीं लगता कि डेटा डिज़ाइन ठीक हो सकता है? यदि उद्देश्य खराब सामान ढूंढना है तो एक या अधिक की गिनती के साथ "एरीके" पर्याप्त है।
पापाराज़ो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.