2010-03-26 16 views
7

मैं विशेष रूप से धीमे डिलीट ऑपरेशन से संबंधित हमारे सिस्टम में से एक में आवर्ती "बग रिपोर्ट" (परफ इश्यू) का विश्लेषण कर रहा हूं। लंबी कहानी छोटी: ऐसा लगता है कि CASCADE DELETE कुंजी काफी हद तक जिम्मेदार थे, और मैं जानना चाहता हूं (ए) अगर यह समझ में आता है, और (बी) यह मामला क्यों है।क्या SQL सर्वर डीआरआई (हटाए गए कैस्केड पर) धीमा है?

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

Widgets 
    | 
    +--- Anvils [1:1] 
    | | 
    | +--- AnvilTestData [1:N] 
    | 
    +--- WidgetHistory (1:N) 
     | 
     +--- WidgetHistoryDetails (1:N) 

स्तंभ परिभाषाएं ऐसा दिखाई देगा:

Widgets (WidgetID int PK, WidgetName varchar(50)) 
Anvils (AnvilID int PK, WidgetID int FK/IX/UNIQUE, ...) 
AnvilTestData (AnvilID int FK/IX, TestID int, ...Test Data...) 
WidgetHistory (HistoryID int PK, WidgetID int FK/IX, HistoryDate datetime, ...) 
WidgetHistoryDetails (HistoryID int FK/IX, DetailType smallint, ...) 

कुछ नहीं भी डरावना, वास्तव में

स्कीमा कुछ इस तरह लग रहा है। Widget विभिन्न प्रकार हो सकते हैं, Anvil एक विशेष प्रकार है, ताकि संबंध 1: 1 (या अधिक सटीक 1: 0..1) हो। फिर वहां बड़ी मात्रा में डेटा है - शायद AnvilTestData प्रति Anvil की हजारों पंक्तियां, समय के साथ एकत्रित, कठोरता, संक्षारण, सटीक वजन, हथौड़ा संगतता, उपयोगिता संबंधी मुद्दों और कार्टून सिर के साथ प्रभाव परीक्षण से निपटने के लिए।

फिर प्रत्येक Widget में विभिन्न प्रकार के लेनदेन का एक लंबा, उबाऊ इतिहास है - उत्पादन, सूची चाल, बिक्री, दोष जांच, आरएमए, मरम्मत, ग्राहक शिकायतें आदि। एक विजेट के लिए 10-20k विवरण हो सकते हैं, या उसकी उम्र के आधार पर कोई भी नहीं।

तो, आश्चर्यजनक रूप से, यहां प्रत्येक स्तर पर CASCADE DELETE संबंध है। यदि Widget को हटाने की आवश्यकता है, तो इसका मतलब है कि कुछ गलत हो गया है और हमें उस विजेट के किसी भी रिकॉर्ड को मिटाना होगा, जिसमें इतिहास, परीक्षण डेटा इत्यादि शामिल हैं। फिर, परमाणु विकल्प।

संबंध सभी अनुक्रमित हैं, आंकड़े अद्यतित हैं। सामान्य प्रश्न तेजी से होते हैं। प्रणाली हटाए गए सब कुछ के लिए बहुत आसानी से hum के साथ hums। हटाने के लिए देख

DELETE FROM Widgets 
WHERE WidgetID = @WidgetID 

सुंदर सरल, अहानिकर ...:

यहाँ बात करने के लिए हो रही है, अंत में, विभिन्न कारणों के लिए हम केवल एक बार में एक विजेट को हटाने तो एक हटाने बयान इस प्रकार दिखाई देगा अनुमति देते हैं, के साथ विजेट के लिए चलाने के लिए 2 मिनट से अधिक समय लगता है!

निष्पादन योजनाओं के माध्यम से slogging के बाद मैं अंततः उच्चतम लागत के साथ उप-संचालन के रूप में AnvilTestData और WidgetHistoryDetails हटा देता है।

DECLARE @AnvilID int 
SELECT @AnvilID = AnvilID FROM Anvils WHERE WidgetID = @WidgetID 

DELETE FROM AnvilTestData 
WHERE AnvilID = @AnvilID 

DELETE FROM WidgetHistory 
WHERE HistoryID IN (
    SELECT HistoryID 
    FROM WidgetHistory 
    WHERE WidgetID = @WidgetID) 

DELETE FROM Widgets WHERE WidgetID = @WidgetID 

इन "अनुकूलन" के दोनों महत्वपूर्ण speedups के परिणामस्वरूप: तो मैं जैसे निम्नलिखित CASCADE बंद करने से (लेकिन वास्तविक FK रखने, बस NO ACTION करने के लिए इसे स्थापित करने) और कुछ के रूप में स्क्रिप्ट को फिर से लिखने में बहुत ज्यादा के साथ प्रयोग , प्रत्येक निष्पादन समय से लगभग एक पूर्ण मिनट शेविंग करता है, ताकि मूल 2-मिनट के विलोपन में लगभग 5-10 सेकंड लगते हैं - कम से कम नए विजेट्स, बिना इतिहास या परीक्षण डेटा के।

बस बिल्कुल स्पष्ट होना करने के लिए, वहां अभी भी है WidgetHistory से WidgetHistoryDetails, जहां फैनआउट सबसे अधिक है करने के लिए एक CASCADE, मैं केवल एक Widgets से होने वाले हटा दिया।

इसके अलावा झरना संबंधों के "सपाट" में हुई उत्तरोत्तर कम नाटकीय लेकिन अभी भी ध्यान देने योग्य speedups, बात करने के लिए जहां को हटाने के लिए एक नई विजेट एक बार लगभग तात्कालिक था झरना के सभी बड़े टेबल हटा दिया और स्पष्ट के साथ प्रतिस्थापित किया गया करने के लिए हटा देता है हटा देता है।

मैं प्रत्येक परीक्षण से पहले DBCC DROPCLEANBUFFERS और DBCC FREEPROCCACHE का उपयोग कर रहा हूं। मैंने उन सभी ट्रिगर्स को अक्षम कर दिया है जो आगे की मंदी पैदा कर सकते हैं (हालांकि वे निष्पादन योजना में वैसे भी दिखाई देंगे)। और मैं पुराने विगेट्स के खिलाफ भी परीक्षण कर रहा हूं, और साथ ही साथ एक महत्वपूर्ण गति को भी देख रहा हूं; हटाए गए जो 5 मिनट लेते थे अब 20-40 सेकंड लेते हैं।

अब मैं "चयन टूटा नहीं गया" दर्शन का उत्साही समर्थक हूं, लेकिन CASCADE DELETE संबंधों की क्रशिंग, दिमाग-दबाने वाली अक्षमता के अलावा इस व्यवहार के लिए कोई तार्किक स्पष्टीकरण नहीं लगता है।

तो, मेरे सवाल कर रहे हैं:

  • इस एसक्यूएल सर्वर में डीआरआई के साथ एक ज्ञात समस्या है? (मैं गूगल पर बात की इस तरह का कोई भी संदर्भ के लिए यहां अतः में लग सकता है या नहीं, मुझे लगता है जवाब नहीं है।)

  • यदि नहीं, तो वहाँ व्यवहार मैं के लिए एक और स्पष्टीकरण है देख के?

  • यदि यह एक ज्ञात मुद्दा है, तो यह एक मुद्दा क्यों है, और क्या बेहतर कामकाज मैं उपयोग कर सकता हूं? , रिकॉर्ड पर आधारित

+0

स्कीमा? यू निश्चित रूप से एफके के एन पक्ष पर एक साधारण लापता सूचकांक नहीं है? –

+0

@ रीमस: उदाहरण स्कीमा है (यदि कोई विवरण गुम है, तो मुझे बताएं कि आप क्या देखना चाहते हैं)। निश्चित रूप से, 100% सकारात्मक यह एक लापता सूचकांक नहीं है (भले ही यह था, फिर भी दूसरा संस्करण धीमा हो जाएगा, है ना?) – Aaronaught

+0

कुछ कॉलम/इंडेक्स/एफके परिभाषाओं में जोड़ा गया है, जो कि मदद करता है। – Aaronaught

उत्तर

8

SQL Server सेट आधारित संचालन में सबसे अच्छा है, जबकि CASCADE विलोपन रहे हैं, उनके स्वभाव से।

SQL Server, अन्य सर्वरों के विपरीत, तत्काल सेट-आधारित संचालन को अनुकूलित करने का प्रयास करता है, हालांकि, यह केवल एक स्तर गहराई से काम करता है। निचले स्तर की टेबल में उन लोगों को हटाने के लिए ऊपरी-स्तर की तालिकाओं में रिकॉर्ड्स को हटाए जाने की आवश्यकता है।

दूसरे शब्दों में, कैस्केडिंग ऑपरेशंस ऊपर-नीचे काम करते हैं, जबकि आपका समाधान डाउन-अप काम करता है, जो अधिक सेट-आधारित और कुशल है।

CREATE TABLE t_g (id INT NOT NULL PRIMARY KEY) 

CREATE TABLE t_p (id INT NOT NULL PRIMARY KEY, g INT NOT NULL, CONSTRAINT fk_p_g FOREIGN KEY (g) REFERENCES t_g ON DELETE CASCADE) 

CREATE TABLE t_c (id INT NOT NULL PRIMARY KEY, p INT NOT NULL, CONSTRAINT fk_c_p FOREIGN KEY (p) REFERENCES t_p ON DELETE CASCADE) 

CREATE INDEX ix_p_g ON t_p (g) 

CREATE INDEX ix_c_p ON t_c (p) 

, इस क्वेरी:

DELETE 
FROM t_g 
WHERE id > 50000 

और उसके योजना:

|--Sequence 
     |--Table Spool 
     | |--Clustered Index Delete(OBJECT:([test].[dbo].[t_g].[PK__t_g__176E4C6B]), WHERE:([test].[dbo].[t_g].[id] > (50000))) 
     |--Index Delete(OBJECT:([test].[dbo].[t_p].[ix_p_g]) WITH ORDERED PREFETCH) 
     | |--Sort(ORDER BY:([test].[dbo].[t_p].[g] ASC, [test].[dbo].[t_p].[id] ASC)) 
     |   |--Table Spool 
     |    |--Clustered Index Delete(OBJECT:([test].[dbo].[t_p].[PK__t_p__195694DD]) WITH ORDERED PREFETCH) 
     |     |--Sort(ORDER BY:([test].[dbo].[t_p].[id] ASC)) 
     |      |--Merge Join(Inner Join, MERGE:([test].[dbo].[t_g].[id])=([test].[dbo].[t_p].[g]), RESIDUAL:([test].[dbo].[t_p].[g]=[test].[dbo].[t_g].[id])) 
     |        |--Table Spool 
     |        |--Index Scan(OBJECT:([test].[dbo].[t_p].[ix_p_g]), ORDERED FORWARD) 
     |--Index Delete(OBJECT:([test].[dbo].[t_c].[ix_c_p]) WITH ORDERED PREFETCH) 
      |--Sort(ORDER BY:([test].[dbo].[t_c].[p] ASC, [test].[dbo].[t_c].[id] ASC)) 
       |--Clustered Index Delete(OBJECT:([test].[dbo].[t_c].[PK__t_c__1C330188]) WITH ORDERED PREFETCH) 
         |--Table Spool 
          |--Sort(ORDER BY:([test].[dbo].[t_c].[id] ASC)) 
           |--Hash Match(Inner Join, HASH:([test].[dbo].[t_p].[id])=([test].[dbo].[t_c].[p])) 
            |--Table Spool 
            |--Index Scan(OBJECT:([test].[dbo].[t_c].[ix_c_p]), ORDERED FORWARD) 

पहले, SQL Servert_g से रिकॉर्ड को हटाता है, तो रिकॉर्ड मिलती

यहां एक नमूना स्कीमा है t_p के साथ हटा दिया गया और बाद वाले से हटा दिया गया, अंत में, के साथ t_p से हटाए गए रिकॉर्ड में शामिल हो गए और t_c से हटा दिए गए।

इस मामले में एक भी तीन-टेबल जॉइन अधिक कुशल होगा, और यह वही है जो आप अपने कामकाज के साथ करते हैं।

यदि यह आपको बेहतर महसूस करता है, तो Oracle किसी भी तरह से कैस्केड ऑपरेशंस को अनुकूलित नहीं करता है: वे हमेशा NESTED LOOPS होते हैं और यदि आप रेफरेंसिंग कॉलम पर इंडेक्स बनाने के लिए भूल जाते हैं तो भगवान आपकी मदद करते हैं।

+0

दिलचस्प ... क्या यह दर्शाता है कि यदि मेरे पास अधिक घोंसले के स्तर हैं, तो 4 या 5 कहें, प्रत्येक 100-1000 पंक्ति fanouts के साथ, यह केवल 'सुरक्षित "(प्रदर्शन-वार) है' CASCADE' संबंधों के ऊपर एक स्तर पत्ती का स्तर? – Aaronaught

+0

@ हारूनॉट: दाएं। स्पूलिंग के लिए अतिरिक्त काम की आवश्यकता होती है और इंडेक्स आंकड़े हमेशा प्रश्नों में सही ढंग से पारित नहीं होते हैं। – Quassnoi

+0

तो अगर मैंने अपना गणित सही किया है, तो 'कैस्केड डेलेटी' मौलिक रूप से एक ओ (एन^एक्स -1) ऑपरेशन है, जिसमें * एन * औसत प्रशंसक और * एक्स * घोंसला स्तर है। डेटाबेस डिज़ाइन पर एक नया नया स्पिन डालता है ... जब भी मैं एक और 'कैस्केड' जोड़ने वाला हूं, घोंसले के स्तर के बारे में सोचना शुरू करना होगा। क्या यह कहीं भी दस्तावेज है? मैं बस सोच रहा हूं कि यह कुछ ऐसा है जो मुझे जाना चाहिए था, या यदि यह उन "छिपी हुई विशेषताओं" में से एक है। – Aaronaught

संबंधित मुद्दे