"परीक्षा (...) या परीक्षा (...) में खंड का क्रम"


11

मेरे पास प्रश्नों का एक वर्ग है जो दो चीजों में से एक के अस्तित्व के लिए परीक्षण करता है। यह रूप का है

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM ...)
  OR EXISTS (SELECT 1 FROM ...)
THEN 1 ELSE 0 END;

वास्तविक विवरण C में उत्पन्न होता है और ODBC कनेक्शन पर एक तदर्थ क्वेरी के रूप में निष्पादित होता है।

यह हाल ही में प्रकाश में आया है कि दूसरा सेलेक्ट शायद ज्यादातर मामलों में पहले सेलेक्ट की तुलना में तेज होगा और दो एक्जिट क्लॉस के ऑर्डर को स्विच करने से कम से कम एक अपमानजनक टेस्ट केस में तेज गति आई जिससे हम अभी बने थे।

करने के लिए स्पष्ट बात यह है कि आगे बढ़ें और दो खंडों को स्विच करें, लेकिन मैं यह देखना चाहता था कि क्या एसक्यूएल सर्वर से अधिक परिचित इस पर वजन करने के लिए परवाह करेंगे। ऐसा लगता है कि मैं संयोग और "कार्यान्वयन विवरण" पर भरोसा कर रहा हूं।

(ऐसा भी लगता है कि यदि SQL सर्वर होशियार था, तो यह समानांतर में दोनों उदाहरणों को निष्पादित करेगा और जो भी एक पहले शॉर्ट-सर्किट दूसरे को पूरा करेगा।)

क्या ऐसी क्वेरी के चलने के समय में सुधार के लिए SQL सर्वर प्राप्त करने का एक बेहतर तरीका है?

अपडेट करें

मेरे प्रश्न में आपके समय और रुचि के लिए धन्यवाद। मैं वास्तविक क्वेरी योजनाओं के बारे में प्रश्नों की उम्मीद नहीं कर रहा था, लेकिन मैं उन्हें साझा करने के लिए तैयार हूं।

यह SQL Server 2008R2 और अधिक से अधिक का समर्थन करने वाले सॉफ़्टवेयर घटक के लिए है। कॉन्फ़िगरेशन और उपयोग के आधार पर डेटा का आकार काफी भिन्न हो सकता है। मेरे सहकर्मी ने क्वेरी में यह बदलाव करने के बारे में सोचा क्योंकि (उदाहरण में) dbf_1162761$z$rv$1257927703तालिका में dbf_1162761$z$dd$1257927703तालिका की तुलना में इसमें पंक्तियों की संख्या हमेशा अधिक या बराबर होगी - कभी-कभी बहुत अधिक (परिमाण के आदेश)।

यहाँ मेरे द्वारा उल्लिखित अपमानजनक मामला है। पहली क्वेरी सबसे धीमी है और इसमें लगभग 20 सेकंड लगते हैं। दूसरी क्वेरी एक पल में पूरी होती है।

इसके लायक क्या है, "OPTIMIZE FOR UNKNOWN" बिट को भी हाल ही में जोड़ा गया था क्योंकि पैरामीटर सूँघना कुछ मामलों को ट्रैश कर रहा था।

मूल प्रश्न:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$rv$1257927703 rv INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=rv.txid WHERE tx.generation BETWEEN 1500 AND 2502)
  OR EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$dd$1257927703 dd INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=dd.txid WHERE tx.generation BETWEEN 1500 AND 2502)
THEN 1 ELSE 0 END
OPTION (OPTIMIZE FOR UNKNOWN)

मूल योजना:

|--Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [Expr1007] THEN (1) ELSE (0) END))
     |--Nested Loops(Left Semi Join, DEFINE:([Expr1007] = [PROBE VALUE]))
          |--Constant Scan
          |--Concatenation
               |--Nested Loops(Inner Join, WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]))
               |    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[PK__dbf_1162__97770A2F62EEAE79] AS [rv]), WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]>(0)))
               |    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[gendex] AS [tx]), SEEK:([tx].[generation] >= (1500) AND [tx].[generation] <= (2502)) ORDERED FORWARD)
               |--Nested Loops(Inner Join, OUTER REFERENCES:([tx].[txid]))
                    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[PK__dbf_1162__E3BA953EC2197789] AS [tx]),  WHERE:([scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]>=(1500) AND [scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]<=(2502)) ORDERED FORWARD)
                    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[n$dbf_1162761$z$dd$txid$1257927703] AS [dd]), SEEK:([dd].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]),  WHERE:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[txid] as [dd].[txid]>(0)) ORDERED FORWARD)

निश्चित क्वेरी:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$dd$1257927703 dd INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=dd.txid WHERE tx.generation BETWEEN 1500 AND 2502)
  OR EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$rv$1257927703 rv INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=rv.txid WHERE tx.generation BETWEEN 1500 AND 2502)
THEN 1 ELSE 0 END
OPTION (OPTIMIZE FOR UNKNOWN)

निश्चित योजना:

|--Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [Expr1007] THEN (1) ELSE (0) END))
     |--Nested Loops(Left Semi Join, DEFINE:([Expr1007] = [PROBE VALUE]))
          |--Constant Scan
          |--Concatenation
               |--Nested Loops(Inner Join, OUTER REFERENCES:([tx].[txid]))
               |    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[PK__dbf_1162__E3BA953EC2197789] AS [tx]),  WHERE:([scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]>=(1500) AND [scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]<=(2502)) ORDERED FORWARD)
               |    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[n$dbf_1162761$z$dd$txid$1257927703] AS [dd]), SEEK:([dd].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]),  WHERE:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[txid] as [dd].[txid]>(0)) ORDERED FORWARD)
               |--Nested Loops(Inner Join, WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]))
                    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[PK__dbf_1162__97770A2F62EEAE79] AS [rv]), WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]>(0)))
                    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[gendex] AS [tx]), SEEK:([tx].[generation] >= (1500) AND [tx].[generation] <= (2502)) ORDERED FORWARD)

जवाबों:


11

अंगूठे के एक सामान्य नियम के रूप में, SQL सर्वर CASEक्रम में एक बयान के कुछ हिस्सों को निष्पादित करेगा लेकिन ORशर्तों को फिर से करने के लिए स्वतंत्र है । कुछ प्रश्नों के लिए आप WHENएक CASEस्टेटमेंट के अंदर भावों के क्रम को बदलकर लगातार बेहतर प्रदर्शन प्राप्त कर सकते हैं । कभी-कभी किसी ORकथन में शर्तों के क्रम को बदलते समय आप बेहतर प्रदर्शन भी प्राप्त कर सकते हैं , लेकिन यह व्यवहार की गारंटी नहीं है।

एक सरल उदाहरण के साथ इसके माध्यम से चलना शायद सबसे अच्छा है। मैं SQL सर्वर 2016 के खिलाफ परीक्षण कर रहा हूं, इसलिए यह संभव है कि आपको अपनी मशीन पर सटीक समान परिणाम नहीं मिलेंगे, लेकिन जहां तक ​​मुझे पता है कि समान सिद्धांत लागू होते हैं। पहले मैं 1 से 1000000 तक दो तालिकाओं में एक मिलियन पूर्णांक, एक गुच्छेदार सूचकांक और एक ढेर के रूप में रखूँगा:

CREATE TABLE dbo.X_HEAP (ID INT NOT NULL, FLUFF VARCHAR(100));

INSERT INTO dbo.X_HEAP  WITH (TABLOCK)
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

CREATE TABLE dbo.X_CI (ID INT NOT NULL, FLUFF VARCHAR(100), PRIMARY KEY (ID));

INSERT INTO dbo.X_CI  WITH (TABLOCK)
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

निम्नलिखित प्रश्न पर विचार करें:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000)
  OR EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000)
THEN 1 ELSE 0 END;

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

इस उदाहरण डेटा के लिए मैं इस तरह से क्वेरी लिखूंगा:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000) THEN 1 
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000) THEN 1 
ELSE 0 END;

यह प्रभावी रूप से SQL सर्वर को पहले क्लस्टर किए गए अनुक्रमणिका के साथ तालिका के विरुद्ध उपकुंजी चलाने के लिए बाध्य करता है। यहाँ से परिणाम हैं SET STATISTICS IO, TIME ON:

तालिका 'X_CI'। स्कैन संख्या 0, तार्किक रीड 3, भौतिक रीड 0

SQL सर्वर निष्पादन समय: CPU समय = 0 ms, बीता समय = 0 ms।

क्वेरी योजना को देखते हुए, यदि लेबल 1 की तलाश में लेबल 2 पर स्कैन की तुलना में कोई डेटा आवश्यक नहीं है और ऐसा नहीं होगा:

अच्छी क्वेरी

निम्नलिखित क्वेरी बहुत कम कुशल है:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000) THEN 1 
  WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000) THEN 1 
ELSE 0 END
OPTION (MAXDOP 1);

क्वेरी प्लान को देखते हुए, हम देखते हैं कि लेबल 2 पर स्कैन हमेशा होता है। यदि एक पंक्ति पाई जाती है तो लेबल 1 की तलाश को छोड़ दिया जाता है। यह वह क्रम नहीं है जो हम चाहते थे:

खराब क्वेरी योजना

प्रदर्शन परिणाम वापस ऊपर है कि:

तालिका 'X_HEAP'। स्कैन गिनती 1, तार्किक 7247 पढ़ता है

SQL सर्वर निष्पादन समय: CPU समय = 15 एमएस, बीता समय = 22 एमएस।

मूल क्वेरी पर वापस जा रहे हैं, इस क्वेरी के लिए मुझे प्रदर्शन के लिए खोज और स्कैन का मूल्यांकन उस क्रम में करना है जो प्रदर्शन के लिए अच्छा है:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000)
  OR EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000)
THEN 1 ELSE 0 END;

और इस क्वेरी में उनका मूल्यांकन विपरीत क्रम में किया जाता है:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000)
  OR EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000)
THEN 1 ELSE 0 END;

हालाँकि, पिछली कुछ क्वेरी के विपरीत, SQL सर्वर क्वेरी ऑप्टिमाइज़र को एक से पहले एक का मूल्यांकन करने के लिए मजबूर करने के लिए कुछ भी नहीं है। आपको किसी भी महत्वपूर्ण चीज के लिए उस व्यवहार पर भरोसा नहीं करना चाहिए।

अंत में, यदि आपको दूसरे से पहले मूल्यांकन करने के लिए एक उपश्रेणी की आवश्यकता है तो CASEआदेश देने के लिए एक बयान या किसी अन्य विधि का उपयोग करें। अन्यथा ORआप चाहते हैं कि एक शर्त में उपश्रेणियों का आदेश देने के लिए स्वतंत्र महसूस करें , लेकिन यह जान लें कि इस बात की कोई गारंटी नहीं है कि लिखित रूप में ऑप्टिमाइज़र उन्हें निष्पादित करेगा।

परिशिष्ट:

एक स्वाभाविक अनुवर्ती प्रश्न यह है कि आप क्या कर सकते हैं यदि आप चाहते हैं कि एसक्यूएल सर्वर तय करे कि कौन सी क्वेरी सस्ती है और उस पहले को निष्पादित करें? अब तक के सभी तरीके SQL Server द्वारा लागू किए गए क्रम में दिखाई देते हैं, जिसमें क्वेरी लिखी गई है, भले ही यह उनमें से कुछ के लिए व्यवहार की गारंटी न हो।

यहाँ एक विकल्प है जो सरल डेमो तालिकाओं के लिए काम करता है:

SELECT CASE
  WHEN EXISTS (
    SELECT 1
    FROM (
        SELECT TOP 2 1 t
        FROM 
        (
            SELECT 1 ID

            UNION ALL

            SELECT TOP 1 ID 
            FROM dbo.X_HEAP 
            WHERE ID = 50000 
        ) h
        CROSS JOIN
        (
            SELECT 1 ID

            UNION ALL

            SELECT TOP 1 ID 
            FROM dbo.X_CI
            WHERE ID = 50000
        ) ci
    ) cnt
    HAVING COUNT(*) = 2
)
THEN 1 ELSE 0 END;

आप यहाँ एक db fiddle डेमो पा सकते हैं । व्युत्पन्न तालिकाओं के क्रम को बदलने से क्वेरी योजना नहीं बदलती है। दोनों प्रश्नों में X_HEAPतालिका को छुआ नहीं गया है। दूसरे शब्दों में, क्वेरी ऑप्टिमाइज़र पहले सस्ती क्वेरी को निष्पादित करने के लिए प्रकट होता है। मैं उत्पादन में इस तरह से कुछ का उपयोग करने की सिफारिश नहीं कर सकता, इसलिए यह यहां ज्यादातर जिज्ञासा मूल्य के लिए है। एक ही चीज़ को पूरा करने का एक बहुत सरल तरीका हो सकता है।


4
या CASE WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000 UNION ALL SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000) THEN 1 ELSE 0 ENDएक विकल्प हो सकता है, हालांकि यह अभी भी मैन्युअल रूप से तय करने पर निर्भर करता है कि कौन सी क्वेरी तेज है और पहले एक डाल रही है। मुझे यकीन नहीं है कि अगर इसे व्यक्त करने का कोई तरीका है ताकि SQL सर्वर स्वचालित रूप से फिर से व्यवस्थित हो जाए, तो सस्ते का स्वचालित रूप से पहले मूल्यांकन किया जाता है।
मार्टिन स्मिथ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.