WHERE IN का उपयोग करके डिलीट ऑपरेशन के दौरान अनपेक्षित स्कैन


40

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

DELETE FROM tblFEStatsBrowsers WHERE BrowserID NOT IN (
    SELECT DISTINCT BrowserID FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID IS NOT NULL
)

tblFEStatsBrowsers को 553 पंक्तियाँ मिली हैं।
tblFEStatsPaperHits को 47.974.301 पंक्तियाँ मिली हैं।

tblFEStatsBrowsers:

CREATE TABLE [dbo].[tblFEStatsBrowsers](
    [BrowserID] [smallint] IDENTITY(1,1) NOT NULL,
    [Browser] [varchar](50) NOT NULL,
    [Name] [varchar](40) NOT NULL,
    [Version] [varchar](10) NOT NULL,
    CONSTRAINT [PK_tblFEStatsBrowsers] PRIMARY KEY CLUSTERED ([BrowserID] ASC)
)

tblFEStatsPaperHits:

CREATE TABLE [dbo].[tblFEStatsPaperHits](
    [PaperID] [int] NOT NULL,
    [Created] [smalldatetime] NOT NULL,
    [IP] [binary](4) NULL,
    [PlatformID] [tinyint] NULL,
    [BrowserID] [smallint] NULL,
    [ReferrerID] [int] NULL,
    [UserLanguage] [char](2) NULL
)

TblFEStatsPaperHits पर एक संकुल सूचकांक है जिसमें BrowserID शामिल नहीं है। इस प्रकार आंतरिक क्वेरी करने के लिए tblFEStatsPaperHits की पूर्ण तालिका स्कैन की आवश्यकता होगी - जो पूरी तरह से ठीक है।

वर्तमान में, tblFEStatsBrowsers में प्रत्येक पंक्ति के लिए एक पूर्ण स्कैन निष्पादित किया गया है, जिसका अर्थ है कि मुझे tblFEStatsPaperHits की 553 पूर्ण तालिका स्कैन मिली है।

सिर्फ WHIS EXISTS को फिर से शुरू करने से योजना में बदलाव नहीं होता है:

DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
    SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
)

हालाँकि, जैसा कि एडम मैकहानिक ने सुझाव दिया था, एचएएसएच जोइन विकल्प को जोड़ने से इष्टतम निष्पादन योजना (tblFEStatsPaperHits का सिर्फ एक स्कैन) होती है:

DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
    SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
) OPTION (HASH JOIN)

अब यह उतना ठीक नहीं है कि इसे कैसे ठीक किया जाए - मैं या तो विकल्प (एचएएसएच जॉइन) का उपयोग कर सकता हूं या मैन्युअल रूप से एक टेबुल टेबल बना सकता हूं। मैं अधिक आश्चर्यचकित हूं कि क्वेरी ऑप्टिमाइज़र कभी भी उस योजना का उपयोग क्यों करेगा जो वर्तमान में करता है।

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

क्या यह इतना सरल है, जितना लगता है, इसका कारण है?

अपडेट 1
कुछ आंकड़े देने के लिए: विकल्प (HASH JOIN):
208.711 तार्किक रीड (12 स्कैन)

विकल्प (LOOP JOIN, HASH GROUP):
11.008.698 तार्किक रीड (~ ब्राउजर प्रति स्कैन (339))

कोई विकल्प नहीं:
11.008.775 तार्किक रीड (~ ब्राउज़रआईडी प्रति स्कैन (339))

अद्यतन 2
उत्कृष्ट जवाब, आप सभी को - धन्यवाद! बस एक लेने के लिए कठिन है। हालांकि मार्टिन पहले था और रेमस एक उत्कृष्ट समाधान प्रदान करता है, मुझे इसे विवरण पर मानसिक रूप से जाने के लिए कीवी को देना होगा :)


5
क्या आप एक आँकड़े से दूसरे सर्वर पर प्रतिलिपि आँकड़ों के अनुसार आँकड़ों को स्क्रिप्ट कर सकते हैं ताकि हम दोहरा सकें?
मार्क स्टोरी-स्मिथ

2
@ MarkStorey-Smith Sure - pastebin.com/9HHRPFgK मान लीजिए कि आप स्क्रिप्ट को खाली डेटाबेस में चलाते हैं, तो यह निष्पादन योजना को दिखाने के साथ-साथ समस्याग्रस्त प्रश्नों को फिर से प्रस्तुत करने में सक्षम बनाता है। स्क्रिप्ट के अंत में दोनों प्रश्न शामिल हैं।
मार्क एस। रासमुसेन

जवाबों:


61

"मैं अधिक आश्चर्यचकित हूं कि क्वेरी ऑप्टिमाइज़र कभी भी उस योजना का उपयोग क्यों करेगा जो वर्तमान में करता है।"

इसे दूसरे तरीके से रखने के लिए, सवाल यह है कि विकल्प के साथ तुलना में निम्नलिखित योजना ऑप्टिमाइज़र के लिए सबसे सस्ती क्यों लगती है (जिनमें से कई हैं )।

मूल योजना

शामिल होने का आंतरिक पक्ष अनिवार्य रूप से प्रत्येक सहसंबंधित मूल्य के लिए निम्नलिखित फ़ॉर्म की एक क्वेरी चला रहा है BrowserID:

DECLARE @BrowserID smallint;

SELECT 
    tfsph.BrowserID 
FROM dbo.tblFEStatsPaperHits AS tfsph 
WHERE 
    tfsph.BrowserID = @BrowserID 
OPTION (MAXDOP 1);

पेपर हिट स्कैन

ध्यान दें कि समानता की तुलना के बाद से पंक्तियों की अनुमानित संख्या 185,220 ( 289,013 नहीं ) है NULL(जब तक कि बाहर नहीं ANSI_NULLSहै OFF)। उपरोक्त योजना की अनुमानित लागत 206.8 इकाई है।

अब एक TOP (1)खंड जोड़ें :

DECLARE @BrowserID smallint;

SELECT TOP (1)
    tfsph.BrowserID 
FROM dbo.tblFEStatsPaperHits AS tfsph 
WHERE 
    tfsph.BrowserID = @BrowserID 
OPTION (MAXDOP 1);

शीर्ष (1) के साथ

अनुमानित लागत अब 0.00452 यूनिट है। शीर्ष भौतिक ऑपरेटर का जोड़ शीर्ष ऑपरेटर पर 1 पंक्ति का एक पंक्ति लक्ष्य निर्धारित करता है । सवाल यह है कि क्लस्टर इंडेक्स स्कैन के लिए एक 'पंक्ति लक्ष्य' कैसे प्राप्त किया जाए; यह है कि, एक पंक्ति को BrowserIDविधेय से मेल खाने से पहले स्कैन को कितनी पंक्तियों को संसाधित करने की अपेक्षा करनी चाहिए ?

उपलब्ध सांख्यिकीय जानकारी में 166 अलग-अलग BrowserIDमूल्य (1 / [सभी घनत्व] = 1 / 0.006024096 = 166) से पता चलता है । लागत मानती है कि अलग-अलग मान भौतिक पंक्तियों में समान रूप से वितरित किए जाते हैं, इसलिए क्लस्टर इंडेक्स स्कैन पर पंक्ति का लक्ष्य 166.302 पर सेट किया गया है (तालिका कार्डिनलिटी में परिवर्तन के लिए लेखांकन के बाद से नमूने एकत्र किए गए थे)।

अनुमानित 166 पंक्तियों को स्कैन करने की अनुमानित लागत बहुत बड़ी नहीं है (प्रत्येक परिवर्तन के लिए एक बार भी 339 बार निष्पादित की गई है BrowserID) - क्लस्टर इंडेक्स स्कैन 1.3219 इकाइयों की अनुमानित लागत को दर्शाता है , जो पंक्ति के लक्ष्य के स्केलिंग प्रभाव को दर्शाता है। आई / ओ और सीपीयू के लिए बिना अनुबंधित लागत क्रमशः 153.931 और 52.8698 के रूप में दिखाई जाती है:

रो गोल ने अनुमानित लागतें लीं

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

यह भी ध्यान रखना दिलचस्प है कि निष्पादन योजना में शीर्ष ऑपरेटर एंटी सेमी जॉइन (विशेष रूप से 'शॉर्ट-सर्कुलेटिंग' मार्टिन उल्लेख) से संबद्ध नहीं है । हम यह देखना शुरू कर सकते हैं कि सबसे पहले GbAggToConstScanOrTop नामक अन्वेषण नियम को अक्षम करके शीर्ष कहां से आता है :

DBCC RULEOFF ('GbAggToConstScanOrTop');
GO
DELETE FROM tblFEStatsBrowsers 
WHERE BrowserID NOT IN 
(
    SELECT DISTINCT BrowserID 
    FROM tblFEStatsPaperHits WITH (NOLOCK) 
    WHERE BrowserID IS NOT NULL
) OPTION (MAXDOP 1, LOOP JOIN, RECOMPILE);
GO
DBCC RULEON ('GbAggToConstScanOrTop');

GbAggToConstScanOrTop अक्षम

उस योजना की अनुमानित लागत 364.912 है , और यह दर्शाता है कि शीर्ष ने एक समूह बाय एग्रीगेट (सहसंबद्ध स्तंभ द्वारा समूहीकरण BrowserID) को बदल दिया । कुल है नहीं निरर्थक की वजह से DISTINCTक्वेरी पाठ में: यह एक अनुकूलन है कि दो अन्वेषण नियमों, द्वारा पेश किया जा सकता है LASJNtoLASJNonDist और LASJOnLclDist । उन दोनों को अक्षम करने के साथ ही इस योजना का निर्माण होता है:

DBCC RULEOFF ('LASJNtoLASJNonDist');
DBCC RULEOFF ('LASJOnLclDist');
DBCC RULEOFF ('GbAggToConstScanOrTop');
GO
DELETE FROM tblFEStatsBrowsers 
WHERE BrowserID NOT IN 
(
    SELECT DISTINCT BrowserID 
    FROM tblFEStatsPaperHits WITH (NOLOCK) 
    WHERE BrowserID IS NOT NULL
) OPTION (MAXDOP 1, LOOP JOIN, RECOMPILE);
GO
DBCC RULEON ('LASJNtoLASJNonDist');
DBCC RULEON ('LASJOnLclDist');
DBCC RULEON ('GbAggToConstScanOrTop');

स्पूल प्लान

उस योजना की अनुमानित लागत 40729.3 इकाई है।

समूह से शीर्ष तक परिवर्तन के बिना, आशावादी 'स्वाभाविक रूप से' BrowserIDविरोधी संयुक्त जुड़ने से पहले एकत्रीकरण के साथ एक हैश ज्वाइन प्लान चुनता है :

DBCC RULEOFF ('GbAggToConstScanOrTop');
GO
DELETE FROM tblFEStatsBrowsers 
WHERE BrowserID NOT IN 
(
    SELECT DISTINCT BrowserID 
    FROM tblFEStatsPaperHits WITH (NOLOCK) 
    WHERE BrowserID IS NOT NULL
) OPTION (MAXDOP 1, RECOMPILE);
GO
DBCC RULEON ('GbAggToConstScanOrTop');

नो टॉप डीओपी 1 प्लान

और MAXDOP 1 प्रतिबंध के बिना, एक समानांतर योजना:

कोई शीर्ष समानांतर योजना नहीं

मूल क्वेरी को 'ठीक' करने का एक और तरीका यह होगा BrowserIDकि निष्पादन योजना रिपोर्ट पर लापता सूचकांक बनाया जाए । नेस्टेड लूप सबसे अच्छा काम करते हैं जब आंतरिक पक्ष अनुक्रमित होता है। सेमी जॉइन के लिए कार्डिनैलिटी का अनुमान लगाना सबसे अच्छे समय में चुनौतीपूर्ण है। उचित अनुक्रमण नहीं होने (बड़ी तालिका में एक अद्वितीय कुंजी भी नहीं है!) बिल्कुल भी मदद नहीं करेगा।

मैंने इसके बारे में Row Goals, Part 4: The Anti Join Anti Pattern में अधिक लिखा ।


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

22

जब मैं केवल एक डेटाबेस बनाने के लिए आपकी स्क्रिप्ट चलाता हूं और प्रश्न में निम्नलिखित प्रश्न प्राप्त करता हूं।

योजना

योजना में दर्शाई गई तालिका कार्डिनलिटीज हैं

  • tblFEStatsPaperHits: 48063400
  • tblFEStatsBrowsers : 339

इसलिए यह अनुमान है कि इसे tblFEStatsPaperHits339 बार स्कैन करने की आवश्यकता होगी । प्रत्येक स्कैन में सहसंबंधित विधेय tblFEStatsBrowsers.BrowserID=tblFEStatsPaperHits.BrowserID AND tblFEStatsPaperHits.BrowserID IS NOT NULLहोता है जिसे स्कैन ऑपरेटर में नीचे धकेल दिया जाता है।

योजना का मतलब यह नहीं है कि हालांकि 339 पूर्ण स्कैन होंगे। के रूप में यह एक विरोधी अर्द्ध शामिल ऑपरेटर के तहत है जैसे ही प्रत्येक स्कैन पर पहली मिलान पंक्ति पाया जाता है यह बाकी के शॉर्ट सर्किट कर सकता है। इस नोड के लिए अनुमानित सबट्री लागत है 1.32603और पूरी योजना की लागत है 1.41337

हैश जॉइन के लिए यह नीचे दी गई योजना देता है

हश ज्वाइन करें

कुल मिलाकर योजना को लागत 418.415( अकेले नेस्टेड छोरों की योजना की तुलना में लगभग 300 गुना अधिक महंगा) के साथ अकेले पूर्ण tblFEStatsPaperHitsलागत वाले सूचकांक पर स्कैन किया जाता 206.8है। 1.32603पहले दिए गए 339 आंशिक स्कैन के लिए अनुमान के साथ इसकी तुलना करें (औसत आंशिक स्कैन अनुमानित लागत = 0.003911592)।

तो यह इंगित करेगा कि यह पूर्ण स्कैन की तुलना में प्रत्येक आंशिक स्कैन की लागत 53,000 गुना कम है। यदि लागत को पंक्ति गणना के साथ रैखिक रूप से स्केल किया जाता है तो इसका मतलब यह होगा कि यह मान रहा है कि औसत रूप से प्रत्येक मिलान पर केवल 900 पंक्तियों को संसाधित करने की आवश्यकता होगी इससे पहले कि यह एक मिलान पंक्ति पाता है और शॉर्ट सर्किट कर सकता है।

मुझे नहीं लगता कि लागत उस रेखीय तरीके से बड़े पैमाने पर करते हैं। मुझे लगता है कि वे निश्चित स्टार्टअप लागत के कुछ तत्व को भी शामिल करते हैं। TOPनिम्नलिखित क्वेरी में विभिन्न मूल्यों की कोशिश कर रहा है

SELECT TOP 147 BrowserID 
FROM [dbo].[tblFEStatsPaperHits] 

147निकटतम अनुमानित सबट्री लागत 0.003911592पर देता है 0.0039113। किसी भी तरह से यह स्पष्ट है कि यह इस धारणा पर लागत को आधार बना रहा है कि प्रत्येक स्कैन में केवल लाखों के बजाय सैकड़ों पंक्तियों के क्रम में तालिका के एक छोटे अनुपात को संसाधित करना होगा।

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


20

मेरी पुस्तक में भी 50M पंक्तियों का एक स्कैन अस्वीकार्य है ... मेरी सामान्य चाल अलग-अलग मूल्यों को मटियामेट करने और इंजन को अप-टू-डेट रखने की है।

create view [dbo].[vwFEStatsPaperHitsBrowserID]
with schemabinding
as
select BrowserID, COUNT_BIG(*) as big_count
from [dbo].[tblFEStatsPaperHits]
group by [BrowserID];
go

create unique clustered index [cdxVwFEStatsPaperHitsBrowserID] 
  on [vwFEStatsPaperHitsBrowserID]([BrowserID]);
go

यह आपको 50M पंक्तियों को स्कैन करने की आवश्यकता को समाप्त करते हुए, ब्राउज़रआईडी प्रति एक भौतिकीकृत सूचकांक एक पंक्ति देता है। इंजन इसे आपके लिए बनाए रखेगा और QO आपके द्वारा पोस्ट किए गए स्टेटमेंट (w / o किसी भी संकेत या क्वेरी को फिर से लिखना) में 'as-is' का उपयोग करेगा।

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


मैं आपको सुनता हूं, कोई भी स्कैन जो निश्चित रूप से आम तौर पर अस्वीकार्य है। इस मामले में यह कुछ एक समय के डेटा सफाई संचालन के लिए है, इसलिए मैं अतिरिक्त अनुक्रमित नहीं बनाने का विकल्प चुन रहा हूं (और यह अस्थायी रूप से ऐसा नहीं कर सकता क्योंकि यह सिस्टम को बाधित करेगा)। मेरे पास ईई नहीं है लेकिन यह देखते हुए कि यह एक बार है, संकेत ठीक होंगे। मेरी मुख्य जिज्ञासा थी कि QO योजना के साथ कैसे उठे हालांकि :) तालिका एक लॉगिंग टेबल है और भारी आवेषण हैं। एक अलग एसिंक्रोनस लॉगिंग टेबल है, हालांकि बाद में यह tblFEStatsPaperHits में पंक्तियों को अपडेट करता है, इसलिए यदि आवश्यक हो, तो मैं इसे स्वयं प्रबंधित कर सकता हूं।
मार्क एस। रासमुसेन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.