OFFSET ... FETCH और पुरानी शैली ROW_NUMBER योजना के बीच निष्पादन योजना के अंतर क्यों हैं?


15

OFFSET ... FETCHSQL सर्वर 2012 के साथ नया मॉडल सरल और तेज पेजिंग प्रदान करता है। इस बात पर विचार करने में कोई अंतर क्यों हैं कि दो रूप शब्दार्थ समान और बहुत सामान्य हैं?

कोई यह मान लेगा कि आशावादी दोनों को पहचानता है और उन्हें (तुच्छ रूप से) पूरी तरह से अनुकूलित करता है।

यहाँ एक बहुत ही सरल मामला है जहाँ OFFSET ... FETCHलागत अनुमान के अनुसार ~ 2x तेजी से है।

SELECT * INTO #objects FROM sys.objects

SELECT *
FROM (
    SELECT *, ROW_NUMBER() OVER (ORDER BY object_id) r
    FROM #objects
) x
WHERE r >= 30 AND r < (30 + 10)
    ORDER BY object_id

SELECT *
FROM #objects
ORDER BY object_id
OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY

ऑफसेट-fetch.png

एक पर एक CI बनाने object_idया फिल्टर जोड़कर इस परीक्षण मामले को अलग-अलग किया जा सकता है लेकिन सभी योजना अंतरों को दूर करना असंभव है। OFFSET ... FETCHहमेशा तेज होता है क्योंकि यह निष्पादन के समय कम काम करता है।


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

जवाबों:


13

प्रश्न में उदाहरण समान परिणाम नहीं देते हैं ( OFFSETउदाहरण में एक-एक त्रुटि है)। नीचे दिए गए अपडेट किए गए फॉर्म उस समस्या को ठीक करते हैं, ROW_NUMBERमामले के लिए अतिरिक्त प्रकार को हटाते हैं , और समाधान को अधिक सामान्य बनाने के लिए चर का उपयोग करते हैं:

DECLARE 
    @PageSize bigint = 10,
    @PageNumber integer = 3;

WITH Numbered AS
(
    SELECT TOP ((@PageNumber + 1) * @PageSize) 
        o.*,
        rn = ROW_NUMBER() OVER (
            ORDER BY o.[object_id])
    FROM #objects AS o
    ORDER BY 
        o.[object_id]
)
SELECT
    x.name,
    x.[object_id],
    x.principal_id,
    x.[schema_id],
    x.parent_object_id,
    x.[type],
    x.type_desc,
    x.create_date,
    x.modify_date,
    x.is_ms_shipped,
    x.is_published,
    x.is_schema_published
FROM Numbered AS x
WHERE
    x.rn >= @PageNumber * @PageSize
    AND x.rn < ((@PageNumber + 1) * @PageSize)
ORDER BY
    x.[object_id];

SELECT
    o.name,
    o.[object_id],
    o.principal_id,
    o.[schema_id],
    o.parent_object_id,
    o.[type],
    o.type_desc,
    o.create_date,
    o.modify_date,
    o.is_ms_shipped,
    o.is_published,
    o.is_schema_published
FROM #objects AS o
ORDER BY 
    o.[object_id]
    OFFSET @PageNumber * @PageSize - 1 ROWS 
    FETCH NEXT @PageSize ROWS ONLY;

ROW_NUMBERयोजना की अनुमानित लागत है 0.0197935 :

पंक्ति संख्या योजना

OFFSETयोजना की अनुमानित लागत है 0.0196955 :

ऑफसेट योजना

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

जहाँ निरंतर शाब्दिक मानों का उपयोग किया जाता है (उदाहरण OFFSET 30में मूल उदाहरण में) ऑप्टिमाइज़र एक पूर्ण प्रकार के बजाय एक शीर्ष के बाद एक टॉपएन सॉर्ट का उपयोग कर सकता है। जब टॉपएन सॉर्ट से आवश्यक पंक्तियाँ एक निरंतर शाब्दिक और <= 100 (का योग OFFSETऔर FETCH) निष्पादन इंजन एक अलग प्रकार के एल्गोरिथ्म का उपयोग कर सकता है जो सामान्यीकृत टॉपएन सॉर्ट की तुलना में तेजी से प्रदर्शन कर सकता है। तीनों मामलों में कुल मिलाकर अलग-अलग प्रदर्शन विशेषताएं हैं।

इस कारण से कि ऑप्टिमाइज़र स्वचालित रूप ROW_NUMBERसे उपयोग करने के लिए सिंटैक्स पैटर्न को रूपांतरित नहीं करता है OFFSET, इसके कई कारण हैं:

  1. एक परिवर्तन लिखना लगभग असंभव है जो सभी मौजूदा उपयोगों से मेल खाएगा
  2. पेजिंग के कुछ सवाल अपने आप बदल गए और दूसरों को भ्रमित नहीं किया जा सका
  3. OFFSETयोजना सभी मामलों में बेहतर होने की गारंटी नहीं है

ऊपर तीसरे बिंदु के लिए एक उदाहरण होता है जहां पेजिंग सेट काफी विस्तृत है। यह एक गैर-अनुक्रमित सूचकांक का उपयोग करके आवश्यक कुंजियों की तलाश करने के लिए बहुत अधिक कुशल हो सकता है और मैन्युअल रूप से सूचकांक को स्कैन करने की तुलना में क्लस्टर इंडेक्स के खिलाफ खोज OFFSETकर सकता है ROW_NUMBERविचार करने के लिए अतिरिक्त मुद्दे हैं कि पेजिंग एप्लिकेशन को यह जानने की जरूरत है कि कुल कितनी पंक्तियां या पेज हैं। यहाँ 'प्रमुख तलाश' और 'ऑफसेट' विधियों के सापेक्ष गुणों की एक और अच्छी चर्चा है

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


1
इसलिए आम मामलों में परिवर्तन नहीं होने का कारण शायद यह है कि स्वीकार्य इंजीनियरिंग ट्रेड-ऑफ खोजना बहुत कठिन था। आपने इस बात के लिए अच्छे कारण दिए हैं कि ऐसा क्यों हो सकता है ।; मुझे कहना होगा कि यह एक अच्छा जवाब है। कई अंतर्दृष्टि और नए विचार। मैं सवाल को थोड़ा और खुला छोड़ दूँगा और फिर सबसे अच्छा जवाब चुनूँगा।
usr

5

आपकी क्वेरी के थोड़े से अंहकार के साथ मुझे बराबर लागत अनुमान (50/50) और बराबर IO आँकड़े मिलते हैं :

; WITH cte AS
(
    SELECT *, ROW_NUMBER() OVER (ORDER BY object_id) r
    FROM #objects
)
SELECT *
FROM cte
WHERE r >= 30 AND r < 40
ORDER BY r

SELECT *
FROM #objects
ORDER BY object_id
OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY

यह आपके संस्करण में दिखाई देने वाली अतिरिक्त छँटाई से बचता rहै object_id


इस अंतर्दृष्टि के लिए धन्यवाद। अब जब मुझे लगता है कि इस बारे में मैंने आशावादी को ROW_NUMBER आउटपुट के क्रमबद्ध स्वरूप को पहले नहीं समझा है। इसे सेट को object_id द्वारा unordered माना जाता है। या कम से कम दोनों को r और object_id द्वारा सॉर्ट नहीं किया गया।
usr

2
@usr आदेश द्वारा उस ROW_NUMBER () का उपयोग यह परिभाषित करता है कि यह संख्याओं को कैसे असाइन करता है। यह आउटपुट ऑर्डर का वादा करने के लिए कुछ भी नहीं करता है - यह अलग है। यह सिर्फ इतना होता है कि यह अक्सर मेल खाता है, लेकिन इसकी गारंटी नहीं है।
हारून बर्ट्रेंड

@AaronBertrand मैं समझता हूं कि ROW_NUMBER आउटपुट का आदेश नहीं देता है। लेकिन अगर ROW_NUMBER को आउटपुट के समान कॉलम द्वारा आदेश दिया जाता है, तो उसी आदेश की गारंटी दी जाती है, है ना? तो क्वेरी ऑप्टिमाइज़र उस तथ्य का उपयोग कर सकता है। इसलिए इस क्वेरी में दो तरह के ऑपरेशन हमेशा गैर-जरूरी होते हैं ।
usr

1
@usr आपने एक सामान्य उपयोग के मामले को हिट किया है जिसका अनुकूलन अनुकूलक खाता नहीं है, लेकिन यह केवल उपयोग का मामला नहीं है। उन मामलों पर विचार करें जहां ROW_NUMBER () के अंदर का ऑर्डर उस कॉलम और कुछ और है। या जब बाहरी क्रम दूसरे कॉलम पर सेकेंडरी छँटाई करता है। या जब आप अवरोही क्रम करना चाहते हैं। या कुछ और पूरी तरह से। मुझे rबेस कॉलम के बजाय अभिव्यक्ति द्वारा आदेश देना पसंद है , यदि केवल इसलिए कि यह एक गैर-नेस्टेड क्वेरी में क्या करता है और एक अभिव्यक्ति द्वारा आदेश देने के साथ मेल खाता है - मैं अभिव्यक्ति को दोहराने के बजाय असाइन किए गए उपनाम का उपयोग करेगा।
हारून बर्ट्रेंड

4
@usr और पॉल की बात पर, ऐसे मामले होने जा रहे हैं, जहां आप ऑप्टिमाइज़र में कार्यक्षमता में अंतराल पा सकते हैं। यदि वे ठीक नहीं होने जा रहे हैं, और आप क्वेरी लिखने का एक बेहतर तरीका जानते हैं, तो बेहतर तरीके का उपयोग करें। रोगी: "डॉक्टर, जब मैं एक्स करता हूं तो दर्द होता है।" डॉक्टर: "एक्स मत करो।" :-)
हारून बर्ट्रेंड

-3

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

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