यह तेज़ क्यों है और क्या इसका उपयोग करना सुरक्षित है? (वर्णमाला में पहला अक्षर कहां है)


10

लंबी कहानी छोटी, हम लोगों की छोटी-छोटी तालिकाएँ अपडेट कर रहे हैं, जिनमें बहुत बड़ी तालिका के लोग हैं। एक हालिया परीक्षण में, इस अपडेट को चलाने में 5 मिनट लगते हैं।

हम जो संभव है कि silliest अनुकूलन की तरह लगता है पर ठोकर खाई, कि प्रतीत होता है पूरी तरह से काम करता है! वही क्वेरी अब 2 मिनट से भी कम समय में चलती है और वही परिणाम पूरी तरह से उत्पन्न करती है।

यहाँ क्वेरी है। अंतिम पंक्ति को "अनुकूलन" के रूप में जोड़ा जाता है। क्वेरी समय में तीव्र कमी क्यों? क्या हम कुछ याद कर रहे हैं? क्या इससे भविष्य में समस्याएं हो सकती हैं?

UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
    AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')

तकनीकी नोट: हम जानते हैं कि परीक्षण करने के लिए अक्षरों की सूची को कुछ और अक्षरों की आवश्यकता हो सकती है। हम "DIFFERENCE" का उपयोग करते समय त्रुटि के लिए स्पष्ट मार्जिन से भी अवगत हैं।

प्रश्न योजना (नियमित): https://www.brentozar.com/pastetheplan/?id=rypV84y7V
क्वेरी योजना ("अनुकूलन" के साथ): https://www.brentozar.com/pastetheplan/?id=r1aC2my7E


4
आपके तकनीकी नोट का छोटा सा उत्तर: AND LEFT(TRIM(largeTbl.last_name), 1) BETWEEN 'a' AND 'z' COLLATE LATIN1_GENERAL_CI_AIआपको वही करना चाहिए जो आपको सभी पात्रों की सूची की आवश्यकता के बिना करना चाहिए और कोड को पढ़ना मुश्किल है
एरिक

क्या आपके पास पंक्तियाँ हैं जहाँ अंतिम स्थिति WHEREगलत है? विशेष रूप से ध्यान दें कि तुलना संवेदनशील हो सकती है।
jpmc26

@ ErikvonAsmuth एक उत्कृष्ट बिंदु बनाता है। लेकिन, बस एक छोटा सा तकनीकी नोट: SQL Server 2008 और 2008 R2 के लिए, "100" कोलाज (यदि उपलब्ध संस्कृति / लोकेल के लिए उपलब्ध हो) के संस्करण का उपयोग करना सबसे अच्छा है। तो वह होगा Latin1_General_100_CI_AI। और SQL सर्वर 2012 और नए (कम से कम SQL सर्वर 2019 के माध्यम से) के लिए, लोकेल के लिए उच्चतम संस्करण में सप्लीमेंट्री कैरेक्टर-इनेबल कॉलेशंस का उपयोग करना सबसे अच्छा है। Latin1_General_100_CI_AI_SCइस मामले में ऐसा ही होगा । संस्करण> 100 (केवल जापानी अब तक) (या आवश्यकता) _SC(जैसे Japanese_XJIS_140_CI_AI) नहीं है।
सोलोमन रटज़की

जवाबों:


9

यह आपकी तालिकाओं, आपके अनुक्रमित आंकड़ों पर निर्भर करता है, .... निष्पादन योजनाओं / io + समय के आंकड़ों की तुलना किए बिना कहने में मुश्किल है।

अंतर मैं उम्मीद करूँगा कि दो टेबल के बीच जोइन से पहले अतिरिक्त फ़िल्टरिंग हो रही है। मेरे उदाहरण में, मैंने अपनी तालिकाओं का पुन: उपयोग करने के लिए चयनों के अपडेट को बदल दिया।

"अनुकूलन" के साथ निष्पादन योजना यहाँ छवि विवरण दर्ज करें

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

आप मेरे परीक्षण डेटा में स्पष्ट रूप से एक फ़िल्टर ऑपरेशन देख रहे हैं, जहाँ कोई रिकॉर्ड नहीं है जहाँ फ़िल्टर किया गया है और परिणामस्वरूप कोई सुधार नहीं हुआ है जहाँ बनाया गया है।

"अनुकूलन" के बिना निष्पादन योजना यहाँ छवि विवरण दर्ज करें

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

फ़िल्टर चला गया है, जिसका अर्थ है कि हमें अनावश्यक रिकॉर्ड को फ़िल्टर करने के लिए जुड़ने पर निर्भर रहना होगा।

अन्य कारण (ओं) क्वेरी को बदलने का एक और कारण / परिणाम हो सकता है, कि क्वेरी को बदलते समय एक नई निष्पादन योजना बनाई गई थी, जो तेजी से होती है। इसका एक उदाहरण एक अलग जॉइन ऑपरेटर चुनने वाला इंजन है, लेकिन वह केवल इस बिंदु पर अनुमान लगा रहा है।

संपादित करें:

दो क्वेरी योजनाओं को प्राप्त करने के बाद स्पष्ट:

क्वेरी बड़ी तालिका से 550M पंक्तियों को पढ़ रही है, और उन्हें फ़िल्टर कर रही है। यहाँ छवि विवरण दर्ज करें

मतलब यह है कि विधेय सबसे अधिक छानने का काम करने वाला है, न कि शोध करने वाला। डेटा में परिणाम पढ़ा जा रहा है, लेकिन जिस तरह से कम किया जा रहा है।

Sql सर्वर बनाना एक अलग इंडेक्स (क्वेरी प्लान) का उपयोग करता है / एक इंडेक्स को जोड़ने से यह हल हो सकता है।

तो ऑप्टिमाइज़ेशन क्वेरी में यह समान समस्या क्यों नहीं है?

क्योंकि एक अलग क्वेरी योजना का उपयोग किया जाता है, एक शोध के बजाय एक स्कैन के साथ।

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

किसी भी प्रकार की तलाश के बिना, लेकिन केवल 4M पंक्तियों के साथ काम करने के लिए वापस आ रहा है।

अगला अंतर

अद्यतन अंतर की उपेक्षा (अनुकूलित क्वेरी पर कुछ भी अपडेट नहीं किया जा रहा है) अनुकूलित क्वेरी पर एक हैश मैच का उपयोग किया जाता है:

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

नेस्टेड लूप के बजाय गैर-अनुकूलित पर शामिल हों:

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

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

अवलोकन

अनुकूलित क्वेरी यहाँ छवि विवरण दर्ज करें

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

गैर-अनुकूलित क्वेरी यहाँ छवि विवरण दर्ज करें गैर-ऑप्टिमाइज़्ड क्वेरी की योजना में कोई समानता नहीं है, एक नेस्टेड लूप जॉइन का उपयोग करता है, और 550M रिकॉर्ड पर अवशिष्ट IO फ़िल्टरिंग करने की आवश्यकता होती है। (अपडेट भी हो रहा है)

गैर-अनुकूलित क्वेरी को बेहतर बनाने के लिए आप क्या कर सकते हैं?

  • कुंजी कॉलम सूची में पहले_नाम और last_name के लिए सूचकांक को बदलना:

    Dbo.largeTableOfPeople (birth_date, first_name, last_name) में शामिल करें (आईडी)

लेकिन कार्यों के उपयोग और इस तालिका के बड़े होने के कारण यह इष्टतम समाधान नहीं हो सकता है।

  • आँकड़ों को अद्यतन करना, बेहतर योजना बनाने और प्राप्त करने के लिए recompile का उपयोग करना।
  • (HASH JOIN, MERGE JOIN)क्वेरी में विकल्प जोड़ना
  • ...

परीक्षण डेटा + क्वेरी का उपयोग किया गया

CREATE TABLE #smallTableOfPeople(importantValue int, birthDate datetime2, first_name varchar(50),last_name varchar(50));
CREATE TABLE #largeTableOfPeople(importantValue int, birth_date datetime2, first_name varchar(50),last_name varchar(50));


set nocount on;
DECLARE @i int = 1
WHILE @i <= 1000
BEGIN
insert into #smallTableOfPeople (importantValue,birthDate,first_name,last_name)
VALUES(NULL, dateadd(mi,@i,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @i += 1;
END


set nocount on;
DECLARE @j int = 1
WHILE @j <= 20000
BEGIN
insert into #largeTableOfPeople (importantValue,birth_Date,first_name,last_name)
VALUES(@j, dateadd(mi,@j,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @j += 1;
END


SET STATISTICS IO, TIME ON;

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å');

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
--AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')




drop table #largeTableOfPeople;
drop table #smallTableOfPeople;

8

यह स्पष्ट नहीं है कि दूसरी क्वेरी वास्तव में एक सुधार है।

निष्पादन योजनाओं में क्वेरीरीस्टैट्स होते हैं जो प्रश्न में बताए गए की तुलना में बहुत कम नाटकीय अंतर दिखाते हैं।

धीमी योजना का समय समाप्त हो गया था 257,556 ms(4 मिनट 17 सेकंड)। 190,992 ms3 के समानांतर की डिग्री के साथ चलने के बावजूद फास्ट प्लान में (3 मिनट 11 सेकंड) का लम्बा समय था ।

इसके अलावा दूसरी योजना एक डेटाबेस में चल रही थी जिसमें शामिल होने के बाद कोई काम नहीं करना था।

पहली योजना

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

दूसरी योजना

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

ताकि 3.5 मिलियन पंक्तियों को अपडेट करने के लिए आवश्यक कार्य द्वारा अतिरिक्त समय को अच्छी तरह से समझाया जा सके (इन पंक्तियों का पता लगाने के लिए अद्यतन ऑपरेटर में आवश्यक कार्य, पृष्ठ को कुंडी लगाना, पृष्ठ को अद्यतन लिखना और लेन-देन लॉग नगण्य नहीं है)

यदि यह वास्तव में इस तरह के साथ तुलना करते समय प्रतिलिपि प्रस्तुत करने योग्य है, तो स्पष्टीकरण यह है कि आप सिर्फ इस मामले में भाग्यशाली हैं।

37 INस्थितियों के साथ फ़िल्टर ने केवल तालिका में 4,008,334 में से 51 पंक्तियों को समाप्त कर दिया, लेकिन आशावादी ने माना कि यह बहुत अधिक समाप्त कर देगा

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

   LEFT(TRIM(largeTbl.last_name), 1) IN ( 'a', 'à', 'á', 'b',
                                          'c', 'd', 'e', 'è',
                                          'é', 'f', 'g', 'h',
                                          'i', 'j', 'k', 'l',
                                          'm', 'n', 'o', 'ô',
                                          'ö', 'p', 'q', 'r',
                                          's', 't', 'u', 'ü',
                                          'v', 'w', 'x', 'y',
                                          'z', 'æ', 'ä', 'ø', 'å' ) 

इस तरह के गलत कार्डिनैलिटी का अनुमान आमतौर पर एक बुरी बात है। इस मामले में इसने एक अलग आकार (और समानांतर) योजना का निर्माण किया जो स्पष्ट रूप से (?) बड़े पैमाने पर कम अंतर के कारण हैश स्पिल के बावजूद आपके लिए बेहतर काम करता है।

बिना TRIMSQL सर्वर बेस कॉलम हिस्टोग्राम में एक सीमा के अंतराल में परिवर्तित करने में सक्षम है और बहुत अधिक सटीक अनुमान देता है लेकिन इसके साथ ही अनुमान लगाने के TRIMलिए रिसॉर्ट्स।

अनुमान की प्रकृति अलग-अलग हो सकती है लेकिन किसी एकल विधेय के लिए अनुमान LEFT(TRIM(largeTbl.last_name), 1)कुछ परिस्थितियों में है * बस अनुमान लगाया जा सकता है table_cardinality/estimated_number_of_distinct_column_values

मुझे यकीन नहीं है कि वास्तव में किन परिस्थितियों में - डेटा का आकार एक भूमिका निभाता है। मैं यहाँ के रूप में विस्तृत निश्चित लंबाई के डेटाटाइप्स के साथ इसे पुन: पेश करने में सक्षम था , लेकिन एक अलग, उच्चतर, अनुमान के साथ मिला varchar(जिसमें सिर्फ एक फ्लैट 10% अनुमान और 100,000 पंक्तियों का अनुमान लगाया गया था)। @ सॉलोमन रुट्स्की बताते हैं कि यदि ट्रेसिंगvarchar(100) रिक्त स्थान के साथ गद्देदार होता है जैसा charकि निचले अनुमान के लिए होता है

इस INसूची का विस्तार किया गया है ORऔर एसक्यूएल सर्वर अधिकतम 4 विधेयकों के साथ घातीय बैकऑफ का उपयोग करता है । तो 219.707अनुमान इस प्रकार है।

DECLARE @TableCardinality FLOAT = 4008334, 
        @DistinctColumnValueEstimate FLOAT = 34207

DECLARE @NotSelectivity float = 1 - (1/@DistinctColumnValueEstimate)

SELECT @TableCardinality * ( 1 - (
@NotSelectivity * 
SQRT(@NotSelectivity) * 
SQRT(SQRT(@NotSelectivity)) * 
SQRT(SQRT(SQRT(@NotSelectivity)))
))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.