मजबूरन प्रवाह भेद


19

मेरे पास इस तरह की एक तालिका है:

CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    ObjectId INT NOT NULL
)

बढ़ती आईडी के साथ वस्तुओं को अनिवार्य रूप से अपडेट करना।

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

मैं इस पाया है एक दिलचस्प अनुकूलन समस्या हो सकता है क्योंकि मैं केवल उन क्वेरी के लिए लिख कर एक अधिकतम इष्टतम क्वेरी योजना उत्पन्न करने में सक्षम किया गया है हो मैं क्या अनुक्रमित की वजह से चाहते हैं, लेकिन नहीं है ऐसा करने के लिए गारंटी है कि मैं क्या करना चाहते हैं:

SELECT DISTINCT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId

@fromUpdateIdएक संग्रहीत प्रक्रिया पैरामीटर कहाँ है।

की एक योजना के साथ:

SELECT <- TOP <- Hash match (flow distinct, 100 rows touched) <- Index seek

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

यह चाल समान क्वेरी योजना (हालांकि निरर्थक TOP के साथ) के परिणामस्वरूप होती है:

WITH ids AS
(
    SELECT ObjectId
    FROM Updates
    WHERE UpdateId > @fromUpdateId
    ORDER BY UpdateId OFFSET 0 ROWS
)
SELECT DISTINCT TOP 100 ObjectId FROM ids

हालांकि, मुझे यकीन नहीं है (और संदेह नहीं) अगर यह सही मायने में आदेश की गारंटी देता है।

एक क्वेरी मुझे आशा थी कि SQL सर्वर को सरल बनाने के लिए पर्याप्त स्मार्ट होगा, लेकिन यह एक बहुत खराब क्वेरी योजना उत्पन्न करता है:

SELECT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId
GROUP BY ObjectId
ORDER BY MIN(UpdateId)

की एक योजना के साथ:

SELECT <- Top N Sort <- Hash Match aggregate (50,000+ rows touched) <- Index Seek

मैं डुप्लिकेट को हटाने के लिए एक इंडेक्स की तलाश में एक इष्टतम योजना UpdateIdऔर एक फ्लो विशिष्ट के साथ एक तरीका खोजने की कोशिश कर रहा हूं ObjectId। कोई विचार?

यदि आप चाहें तो नमूना डेटा । ऑब्जेक्ट्स शायद ही कभी एक से अधिक अद्यतन होंगे, और लगभग 100 पंक्तियों के एक सेट के भीतर लगभग कभी भी एक से अधिक नहीं होना चाहिए, यही कारण है कि मैं एक प्रवाह के बाद हूं , जब तक कि कुछ बेहतर न हो मुझे पता नहीं है? हालाँकि, इस बात की कोई गारंटी नहीं है कि किसी ObjectIdतालिका में 100 से अधिक पंक्तियाँ नहीं होंगी। तालिका में 1,000,000 पंक्तियाँ हैं और इसके तेजी से बढ़ने की उम्मीद है।

मान लें कि इसके उपयोगकर्ता के पास उपयुक्त अगला खोजने का एक और तरीका है @fromUpdateId। इस क्वेरी में इसे वापस करने की आवश्यकता नहीं है।

जवाबों:


15

SQL सर्वर ऑप्टिमाइज़र आपके द्वारा आवश्यक गारंटी के साथ निष्पादन योजना का उत्पादन नहीं कर सकता है, क्योंकि हैश मैच फ़्लो डिस्टिक्ट ऑपरेटर ऑर्डर-संरक्षण नहीं है।

हालांकि, मुझे यकीन नहीं है (और संदेह नहीं) अगर यह सही मायने में आदेश की गारंटी देता है।

आप कई मामलों में आदेश संरक्षण का पालन कर सकते हैं , लेकिन यह एक कार्यान्वयन विवरण है; कोई गारंटी नहीं है, इसलिए आप इस पर भरोसा नहीं कर सकते। हमेशा की तरह, प्रस्तुति आदेश केवल एक शीर्ष-स्तरीय ORDER BYखंड द्वारा गारंटी दी जा सकती है ।

उदाहरण

नीचे दी गई स्क्रिप्ट से पता चलता है कि हैश मैच फ्लो डिस्टि्रक्ट ऑर्डर को संरक्षित नहीं करता है। यह दोनों स्तंभों में मिलान संख्या 1-50,000 के साथ प्रश्न में तालिका सेट करता है:

IF OBJECT_ID(N'dbo.Updates', N'U') IS NOT NULL
    DROP TABLE dbo.Updates;
GO
CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1),
    ObjectId INT NOT NULL,

    CONSTRAINT PK_Updates_UpdateId PRIMARY KEY (UpdateId)
);
GO
INSERT dbo.Updates (ObjectId)
SELECT TOP (50000)
    ObjectId =
        ROW_NUMBER() OVER (
            ORDER BY C1.[object_id]) 
FROM sys.columns AS C1
CROSS JOIN sys.columns AS C2
ORDER BY
    ObjectId;

परीक्षण क्वेरी है:

DECLARE @Rows bigint = 50000;

-- Optimized for 1 row, but will be 50,000 when executed
SELECT DISTINCT TOP (@Rows)
    U.ObjectId 
FROM dbo.Updates AS U
WHERE 
    U.UpdateId > 0
OPTION (OPTIMIZE FOR (@Rows = 1));

अनुमानित योजना सूचकांक की तलाश और प्रवाह को अलग दिखाती है:

अनुमानित योजना

निश्चित रूप से उत्पादन के साथ शुरू करने का आदेश दिया लगता है:

परिणामों की शुरुआत

... लेकिन इसके बाद के मूल्य 'गायब' होने लगते हैं:

पैटर्न टूट रहा है

...और आखिरकार:

अराजकता फूट पड़ी

इस विशेष मामले में स्पष्टीकरण यह है कि हैश ऑपरेटर फैलता है:

निष्पादन के बाद की योजना

एक बार एक विभाजन फैल जाता है, उसी विभाजन में हैश करने वाली सभी पंक्तियाँ भी फैल जाती हैं। स्पिलिट विभाजन बाद में संसाधित किए जाते हैं, इस उम्मीद को तोड़ते हुए कि प्राप्त किए गए विशिष्ट मूल्यों को तुरंत उसी क्रम में उत्सर्जित किया जाएगा जो उन्हें प्राप्त हुए हैं।


इच्छित परिणाम के उत्पादन के लिए एक कुशल क्वेरी लिखने के कई तरीके हैं, जैसे पुनरावृत्ति या कर्सर का उपयोग करना। हालांकि, यह हैश मैच फ्लो डिस्टिंच का उपयोग करके नहीं किया जा सकता है ।


11

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

मैंने उन स्तंभों के संयोजन के बारे में सोचने की कोशिश करके इस समस्या से संपर्क किया, ORDER BYजिन्हें लागू DISTINCTकरने के बाद मैं सही परिणाम प्राप्त कर सका । UpdateIdप्रति ObjectIdके साथ न्यूनतम मूल्य ObjectIdएक ऐसा संयोजन है। हालांकि, सीधे न्यूनतम मांगने UpdateIdसे परिणाम तालिका से सभी पंक्तियों को पढ़ने में लगता है। इसके बजाय, हम अप्रत्यक्ष रूप UpdateIdसे तालिका में एक और शामिल होने के साथ न्यूनतम मूल्य के लिए पूछ सकते हैं । आदेश में Updatesतालिका को स्कैन करने के लिए विचार है , किसी भी पंक्तियों को बाहर फेंक दें जिसके UpdateIdलिए उस पंक्ति का न्यूनतम मूल्य नहीं है ObjectId, और पहले 100 पंक्तियों को रखें। डेटा वितरण के आपके विवरण के आधार पर हमें बहुत अधिक पंक्तियों को फेंकने की आवश्यकता नहीं है।

डेटा प्रस्तुत करने के लिए, मैंने प्रत्येक अलग ऑब्जेक्ट के लिए 2 पंक्तियों के साथ एक तालिका में 1 मिलियन पंक्तियों को रखा:

INSERT INTO Updates WITH (TABLOCK)
SELECT t.RN / 2
FROM 
(
    SELECT TOP 1000000 -1 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t;

CREATE INDEX IX On Updates (Objectid, UpdateId);

गैर-अनुक्रमित सूचकांक पर Objectidऔर UpdateIdमहत्वपूर्ण है। यह हमें कुशलतापूर्वक उन पंक्तियों को बाहर निकालने की अनुमति देता है जिनमें न्यूनतम UpdateIdप्रति नहीं है Objectid। क्वेरी लिखने के कई तरीके हैं जो ऊपर दिए गए विवरण से मेल खाते हैं। यहाँ एक तरीका है NOT EXISTS:

DECLARE @fromUpdateId INT = 9999;
SELECT ObjectId
FROM (
    SELECT DISTINCT TOP 100 u1.UpdateId, u1.ObjectId
    FROM Updates u1
    WHERE UpdateId > @fromUpdateId
    AND NOT EXISTS (
        SELECT 1
        FROM Updates u2
        WHERE u2.UpdateId > @fromUpdateId
        AND u1.ObjectId = u2.ObjectId
        AND u2.UpdateId < u1.UpdateId
    )
    ORDER BY u1.UpdateId, u1.ObjectId
) t;

यहाँ क्वेरी योजना की एक तस्वीर है :

क्वेरी योजना

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

तालिका 'अपडेट'। स्कैन गिनती 10000, तार्किक रीड 31900, भौतिक रीड 0

SQL सर्वर निष्पादन समय: CPU समय = 31 एमएस, बीता हुआ समय = 42 एमएस।


9

मुझे सवाल पसंद है - फ़्लो डिस्टिक्ट मेरे पसंदीदा ऑपरेटरों में से एक है।

अब, गारंटी समस्या है। जब आप FD ऑपरेटर के लिए ऑर्डर किए गए फ़ैशन में सीक ऑपरेटर से पंक्तियों को खींचने के बारे में सोचते हैं, तो प्रत्येक पंक्ति का उत्पादन करना क्योंकि यह अद्वितीय होने के लिए निर्धारित करता है, इससे आपको सही क्रम में पंक्तियाँ मिलेंगी। लेकिन यह जानना मुश्किल है कि क्या कुछ परिदृश्य हो सकते हैं जहां एफडी एक बार में एक पंक्ति को नहीं संभालता है।

सैद्धांतिक रूप से, एफडी सीक से 100 पंक्तियों का अनुरोध कर सकता है, और उन्हें जिस भी क्रम में उनकी आवश्यकता है, उनका उत्पादन कर सकता है।

क्वेरी संकेत OPTION (FAST 1, MAXDOP 1)मदद कर सकता है, क्योंकि यह सीक ऑपरेटर से अधिक पंक्तियों को प्राप्त करने से बचाएगा। हालांकि यह एक गारंटी है? काफी नहीं। यह अभी भी एक समय में पंक्तियों के एक पृष्ठ को खींचने का निर्णय ले सकता है, या ऐसा कुछ।

मुझे लगता है कि OPTION (FAST 1, MAXDOP 1), आपका OFFSETसंस्करण आपको ऑर्डर के बारे में बहुत विश्वास दिलाएगा, लेकिन इसकी कोई गारंटी नहीं है।


जैसा कि मैंने इसे समझा है, समस्या यह है कि फ़्लो डिस्टिक्ट ऑपरेटर एक हैश तालिका का उपयोग करता है जो डिस्क पर फैल सकता है। जब एक स्पिल होता है, तो रैम में अभी भी भाग का उपयोग करके संसाधित की जा सकने वाली पंक्तियों को तुरंत संसाधित किया जाता है, लेकिन अन्य पंक्तियों को तब तक संसाधित नहीं किया जाता है जब तक कि स्पिल्ड डेटा डिस्क से वापस नहीं पढ़ा जाता है। मैं जो बता सकता हूं, वह हैश टेबल (जैसे हैश जॉइन) का उपयोग करने वाले किसी भी ऑपरेटर को उसके स्पिलिंग व्यवहार के कारण ऑर्डर को संरक्षित करने की गारंटी नहीं है।
sam.bishop

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