ORDER BY को क्वेरी में शामिल करने से कोई पंक्तियाँ नहीं लौटती हैं जो प्रदर्शन को बहुत प्रभावित करती हैं


15

एक साधारण तीन तालिका में शामिल होने पर, क्वेरी का प्रदर्शन काफी बदल जाता है जब ORDER BY को शामिल किया जाता है, यहां तक ​​कि कोई पंक्तियां भी नहीं लौटी हैं। वास्तविक समस्या परिदृश्य शून्य पंक्तियों को वापस करने के लिए 30 सेकंड लेता है, लेकिन तत्काल जब ORDER द्वारा शामिल नहीं किया जाता है। क्यों?

SELECT * 
FROM tinytable t                          /* one narrow row */
JOIN smalltable s on t.id=s.tinyId        /* one narrow row */
JOIN bigtable b on b.smallGuidId=s.GuidId /* a million narrow rows */
WHERE t.foreignId=3                       /* doesn't match */
ORDER BY b.CreatedUtc          /* try with and without this ORDER BY */

मैं समझता हूं कि मेरे पास bigtable.smallGuidId पर एक सूचकांक हो सकता है, लेकिन, मुझे विश्वास है कि वास्तव में इस मामले में इसे बदतर बना देगा।

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

CREATE TABLE tinytable
  (
     id        INT PRIMARY KEY IDENTITY(1, 1),
     foreignId INT NOT NULL
  )

CREATE TABLE smalltable
  (
     id     INT PRIMARY KEY IDENTITY(1, 1),
     GuidId UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(),
     tinyId INT NOT NULL,
     Magic  NVARCHAR(max) NOT NULL DEFAULT ''
  )

CREATE TABLE bigtable
  (
     id          INT PRIMARY KEY IDENTITY(1, 1),
     CreatedUtc  DATETIME NOT NULL DEFAULT GETUTCDATE(),
     smallGuidId UNIQUEIDENTIFIER NOT NULL
  )

INSERT tinytable
       (foreignId)
VALUES(7)

INSERT smalltable
       (tinyId)
VALUES(1)

-- make a million rows 
DECLARE @i INT;

SET @i=20;

INSERT bigtable
       (smallGuidId)
SELECT GuidId
FROM   smalltable;

WHILE @i > 0
  BEGIN
      INSERT bigtable
             (smallGuidId)
      SELECT smallGuidId
      FROM   bigtable;

      SET @i=@i - 1;
  END 

मैंने उसी परिणामों के साथ SQL 2005, 2008 और 2008R2 पर परीक्षण किया है।

जवाबों:


32

मैं मार्टिन स्मिथ के जवाब से सहमत हूं, लेकिन समस्या केवल आंकड़ों में से एक नहीं है, बिल्कुल। फॉरेनआईड कॉलम के आंकड़े (स्वचालित आंकड़ों को संभालने में सक्षम हैं) सटीक रूप से दिखाते हैं कि 3 के मूल्य के लिए कोई पंक्तियाँ मौजूद नहीं हैं (7 के मूल्य के साथ सिर्फ एक है):

DBCC SHOW_STATISTICS (tinytable, foreignId) WITH HISTOGRAM

सांख्यिकी उत्पादन

SQL सर्वर जानता है कि आंकड़े कैप्चर किए जाने के बाद से चीजें बदल सकती हैं, इसलिए योजना निष्पादित होने पर मान 3 के लिए एक पंक्ति हो सकती है । इसके अलावा, योजना संकलन और निष्पादन के बीच समय की कोई भी राशि समाप्त हो सकती है (योजनाएं पुन: उपयोग के लिए कैश की जाती हैं, सब के बाद)। जैसा कि मार्टिन कहते हैं, SQL सर्वर में तर्क का पता लगाने के लिए तर्क है कि जब इष्टतम संशोधनों के लिए किसी भी कैश्ड योजना को फिर से तैयार करने के लिए पर्याप्त संशोधन किए गए हैं।

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

इसके बजाय अनुकूलक का मॉडल न्यूनतम एक पंक्ति का अनुमान लगाता है । इस अनुमान को रोजगार देने से औसतन बेहतर योजनाएं बनती हैं, अगर ऐसा अनुमान लगाया जाए तो कम अनुमान संभव है। एक योजना जो किसी स्तर पर शून्य-पंक्ति अनुमान पैदा करती है, प्रसंस्करण बिंदु पर उस बिंदु से बेकार हो जाएगी, क्योंकि लागत-आधारित निर्णय लेने के लिए कोई आधार नहीं होगा (शून्य पंक्तियाँ शून्य पंक्तियाँ हैं कोई फर्क नहीं पड़ता)। यदि अनुमान गलत हो जाता है, तो शून्य पंक्ति अनुमान से ऊपर की योजना आकार लगभग उचित होने की संभावना नहीं है।

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

अनिवार्य रूप से, आपके पास यहां एक ऐसी क्वेरी है जो ऑप्टिमाइज़र के मॉडल के अनुरूप नहीं है। मल्टी-कॉलम या फ़िल्टर्ड इंडेक्स के साथ अनुमानों को 'बेहतर' करने के लिए हम कुछ नहीं कर सकते हैं; यहाँ 1 पंक्ति से कम अनुमान लगाने का कोई तरीका नहीं है। एक वास्तविक डेटाबेस में यह सुनिश्चित करने के लिए विदेशी कुंजी हो सकती है कि यह स्थिति उत्पन्न न हो, लेकिन यह मानते हुए कि यहां लागू नहीं है, हम आउट-ऑफ-मॉडल स्थिति को सही करने के लिए संकेत का उपयोग करने से बचे हैं। इस संकेत के साथ किसी भी संख्या में अलग-अलग संकेत दृष्टिकोण काम करेंगे। OPTION (FORCE ORDER)वह है जो लिखित रूप में क्वेरी के साथ अच्छी तरह से काम करता है।


21

यहां मूल समस्या आंकड़ों में से एक है।

दोनों प्रश्नों के लिए अनुमानित पंक्ति गणना से पता चलता है कि यह विश्वास करता है कि अंतिम SELECT1,048,580 पंक्तियों (वर्तमान में मौजूद अनुमानित संख्याओं की संख्या bigtable) के बजाय 0 होगा जो वास्तव में सुनिश्चित करता है।

आपकी दोनों JOINस्थितियां मेल खाती हैं और सभी पंक्तियों को संरक्षित करेगी। वे समाप्त हो रहे हैं क्योंकि एकल पंक्ति विधेय से tinytableमेल नहीं खाती t.foreignId=3है।

अगर तुम दौड़ते हो

SELECT * 
FROM tinytable t  
WHERE t.foreignId=3  AND id=1 

और इसके 1बजाय पंक्तियों की अनुमानित संख्या को देखें 0और यह त्रुटि पूरे योजना में फैलती है। tinytableवर्तमान में 1 पंक्ति शामिल है। इस पंक्ति के लिए आँकड़े तब तक recompiled नहीं होंगे, जब तक कि 500 पंक्ति संशोधन नहीं हो जाते हैं, इसलिए एक मिलान पंक्ति जोड़ी जा सकती है और यह एक recompile को ट्रिगर नहीं करेगा।

जब आप ORDER BYक्लॉज जोड़ते हैं तो ज्वाइन ऑर्डर बदलता है और इसमें एक varchar(max)कॉलम होने smalltableका कारण यह है कि यह अनुमान लगाता है कि varchar(max)कॉलम औसतन 4,000 बाइट्स द्वारा पंक्तियों को बढ़ाएगा। 1048580 पंक्तियों से गुणा करें और इसका मतलब है कि सॉर्ट ऑपरेशन को अनुमानित 4GB की आवश्यकता होगी, इसलिए यह समझदारी से SORTऑपरेशन से पहले करने का निर्णय लेता है JOIN

आप नीचे दिए गए संकेतों के उपयोग के साथ ORDER BYगैर- ORDER BYसम्मिलित रणनीति को अपनाने के लिए क्वेरी को बाध्य कर सकते हैं।

SELECT *
FROM   tinytable t /* one narrow row */
       INNER MERGE JOIN smalltable s /* one narrow row */
                        INNER LOOP JOIN bigtable b
                          ON b.smallGuidId = s.GuidId /* a million narrow rows */
         ON t.id = s.tinyId
WHERE  t.foreignId = 3 /* doesn't match */
ORDER  BY b.CreatedUtc
OPTION (MAXDOP 1) 

योजना लगभग 12,000और गलत अनुमानित पंक्ति गणना और अनुमानित डेटा आकार की अनुमानित उप ट्री लागत के साथ एक सॉर्ट ऑपरेटर दिखाती है ।

योजना

BTW मुझे UNIQUEIDENTIFIERपूर्णांक वाले स्तंभों को बदलने की जगह नहीं मिली, जिन्होंने मेरे परीक्षण में चीजों को बदल दिया।


2

अपने शो एक्ज़ीक्यूशन प्लान बटन को चालू करें और आप देख सकते हैं कि क्या हो रहा है। यहाँ "धीमी" क्वेरी के लिए योजना है: यहाँ छवि विवरण दर्ज करें

और यहाँ "तेज" क्वेरी है: यहाँ छवि विवरण दर्ज करें

उस पर नजर डालें - एक साथ चलाएं, पहली क्वेरी ~ 33x अधिक "महंगी" (97: 3 अनुपात) है। एसक्यूएल डेटायट द्वारा बिगटेबल को ऑर्डर करने के लिए एसक्यूएल पहली क्वेरी का अनुकूलन कर रहा है, फिर स्मॉलटेबल और टाइनीटेबल पर एक छोटा "सीक" लूप चला रहा है, उन्हें प्रत्येक 1 मिलियन बार निष्पादित कर रहा है (आप अधिक आँकड़े प्राप्त करने के लिए "क्लस्टर इंडेक्स सीक आइकन पर मँडरा सकते हैं")। तो, छोटे तालिकाओं (23% और 46%) पर सॉर्ट (27%), और 2 एक्स 1 मिलियन "महंगी क्वेरी के बड़े पैमाने पर" हैं। इसकी तुलना में, गैर- ORDER BYक्वेरी कुल 3 स्कैन करती है।

असल में, आपने अपने विशेष परिदृश्य के लिए SQL अनुकूलक तर्क में एक छेद पाया है। लेकिन जैसा कि TysHTTP द्वारा कहा गया है, यदि आप एक इंडेक्स जोड़ते हैं (जो आपके इंसर्ट / कुछ अपडेट को धीमा कर देता है), तो आपकी स्कैनिंग तेजी से क्रेजी हो जाती है।


2

क्या हो रहा है एसक्यूएल प्रतिबंध से पहले आदेश को चलाने का फैसला कर रहा है।

इसे इस्तेमाल करे:

SELECT *
(
SELECT * 
FROM tinytable t
    INNER JOIN smalltable s on t.id=s.tinyId
    INNER JOIN bigtable b on b.smallGuidId=s.GuidId
WHERE t.foreignId=3
) X
ORDER BY b.CreatedUtc

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

अंत में, निम्न स्क्रिप्ट को चलाने का प्रयास करें और फिर देखें कि अपडेट किए गए आँकड़े और अनुक्रमणिका आपके द्वारा की जा रही समस्या को ठीक करते हैं या नहीं:

EXEC [sp_MSforeachtable] @command1="RAISERROR('UPDATE STATISTICS(''?'') ...',10,1) WITH NOWAIT UPDATE STATISTICS ? "

EXEC [sp_MSforeachtable] @command1="RAISERROR('DBCC DBREINDEX(''?'') ...',10,1) WITH NOWAIT DBCC DBREINDEX('?')"

EXEC [sp_MSforeachtable] @command1="RAISERROR('UPDATE STATISTICS(''?'') ...',10,1) WITH NOWAIT UPDATE STATISTICS ? "

1

आपको फ़ील्ड (ओं) द्वारा अपने आदेश के लिए एक सूचकांक जोड़ना चाहिए और आप देखेंगे कि गति बढ़ जाएगी। Https://stackoverflow.com/questions/1716798/sql-server-2008-ordering-by-datetime-is-too-slow देखें

यह कोशिश करो, मुझे नहीं लगता कि आपका अनुमान है, कि यह केवल चीजों को धीमा कर देगा, सही है।

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