बिना किसी खंड के एक विशाल DELETE FROM <table> को गति देने के तरीके


37

SQL सर्वर 2005 का उपयोग करना।

मैं एक बहुत बड़ा प्रदर्शन कर रहा हूँ जहाँ कोई खंड नहीं है। यह मूल रूप से TRUNCATE TABLE स्टेटमेंट के बराबर है - सिवाय इसके कि मुझे TRUNCATE का उपयोग करने की अनुमति नहीं है। समस्या यह है कि तालिका बहुत बड़ी है - 10 मिलियन पंक्तियाँ, और इसे पूरा करने में एक घंटे से अधिक का समय लगता है। क्या बिना इसे तेजी से बनाने का कोई तरीका है:

  • Truncate का उपयोग करना
  • अनुक्रमणिका को अक्षम या छोड़ रहा है?

टी-लॉग पहले से ही एक अलग डिस्क पर है।

किसी भी सुझाव का स्वागत है!


2
यदि आप यह बहुत कुछ कर रहे हैं, तो तालिका को विभाजित करने पर विचार करें
गयूस

1
क्या आप TRUNCATE का उपयोग नहीं कर सकते क्योंकि तालिका में संदर्भित FK बाधाएं हैं?
निक चामास

जवाबों:


39

आप जो कर सकते हैं, वह इस तरह से हटा दिया गया है:

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable

जहां xxx है, कहते हैं, 50000

इसका एक संशोधन, यदि आप बहुत अधिक प्रतिशत पंक्तियों को निकालना चाहते हैं ...

SELECT col1, col2, ... INTO #Holdingtable
           FROM MyTable WHERE ..some condition..

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable WHERE ...

INSERT MyTable (col1, col2, ...)
           SELECT col1, col2, ... FROM #Holdingtable

3
@tuseau: प्रत्येक डिलीट को रोलबैक करने में त्रुटि के मामले में कुछ लॉग स्पेस की आवश्यकता होती है। एक 50k पंक्ति हटाना 10 मीटर पंक्ति हटाने की तुलना में कम संसाधन / स्थान लेता है। बेशक, लॉग बैकअप अभी भी आदि चलाते हैं और जगह लेते हैं, लेकिन सर्वर पर बहुत से छोटे बैचों के लिए यह आसान है कि बड़े से बड़ा मैकरिंग किया जाए।
GBN

1
धन्यवाद, बैच हटाना थोड़ा मदद करता है, मुझे लगता है कि यह सबसे अच्छा विकल्प है।
tuseau

2
@ पैल हेलमर: अगर बैच डिलीट लेनदेन में है तो कोई फायदा नहीं है। अन्यथा प्रत्येक लॉग लेखन छोटा होता है, बस, एक आसान भार
gbn

1
एक और टिप्पणी: बैच डिलीट बहुत मदद करता है, और 1 घंटे 42 मिनट से 3 मिनट तक 20 मिलियन पंक्तियों को हटाने में मदद करता है - लेकिन सुनिश्चित करें कि तालिका में एक क्लस्टर इंडेक्स है! यदि यह एक ढेर है, तो शीर्ष खंड निष्पादन योजना में एक प्रकार बनाता है जो किसी भी सुधार को नकारता है। बाद में स्पष्ट लगता है।
tuseau

2
@Noumenon: यह @@ ROWCOUNT सुनिश्चित करता है 1 है
GBN

21

इसे आसानी से पूरा करने के लिए आप शीर्ष खंड का उपयोग कर सकते हैं:

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END

घुंघराले कोष्ठक आपके कोड को प्रारूपित करते हैं
gbn

@ यह तो एसओ पर है। यहाँ यह अभी भी 101 010 है।
bernd_k

7

यदि आप TRUNCATE का उपयोग करने में सक्षम नहीं हैं, तो मैं आपके डिलीट किए गए सुझावों को बैचने के सुझावों से सहमत हूं और मुझे इसकी मौलिकता के लिए ड्रॉप / क्रिएट सुझाव पसंद है, लेकिन मैं आपके प्रश्न में निम्नलिखित टिप्पणी के बारे में उत्सुक हूं:

यह मूल रूप से TRUNCATE TABLE स्टेटमेंट के बराबर है - सिवाय इसके कि मुझे TRUNCATE का उपयोग करने की अनुमति नहीं है

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

यह मानते हुए कि मैं सोच रहा हूं कि क्या एक संग्रहीत प्रक्रिया बनाई गई है जो TRUNCATE TABLE का उपयोग करती है और "EXECUTE AS" का उपयोग करती है, सीधे तालिका को छोटा करने के लिए आवश्यक सुरक्षा अधिकार देने के लिए एक व्यवहार्य विकल्प माना जाएगा।

उम्मीद है, इससे आपको सुरक्षा संबंधी चिंताओं को दूर करने के साथ-साथ आपको वह गति भी मिलेगी, जो आपकी कंपनी को अपने खाते को db_ddladmin भूमिका में जोड़ने से हो सकती है।

इस तरह संग्रहीत प्रक्रिया का उपयोग करने का एक और लाभ यह है कि संग्रहीत प्रक्रिया को स्वयं बंद किया जा सकता है ताकि केवल विशिष्ट खाते (खातों) को ही इसका उपयोग करने की अनुमति दी जाए।

यदि किसी कारण से यह स्वीकार्य समाधान नहीं है और आपके द्वारा हटाए गए इस तालिका में डेटा होना आवश्यक है, तो यह एक ऐसा काम है जो दिन / घंटे / दिन में एक बार किया जाना चाहिए, मैं अनुरोध करूंगा कि तालिका को कम करने के लिए एक SQL एजेंट नौकरी बनाई गई थी प्रत्येक दिन एक निर्धारित समय पर।

उम्मीद है की यह मदद करेगा!


5

ट्रंकट को छोड़कर .. केवल बैचों में हटाएं आपकी मदद कर सकते हैं।

आप मेज को गिरा सकते हैं और इसे फिर से बना सकते हैं, सभी बाधाओं और सूचकांक के साथ, बिल्कुल। प्रबंधन स्टूडियो में आपके पास ड्रॉप करने और बनाने के लिए एक तालिका को स्क्रिप्ट करने का विकल्प होता है, इसलिए यह एक तुच्छ विकल्प होना चाहिए। लेकिन यह केवल अगर आपको डीडीएल कार्रवाई करने की अनुमति है, जो मुझे लगता है कि यह वास्तव में एक विकल्प नहीं है।


क्योंकि एप्लिकेशन को समवर्ती संचालन के लिए डिज़ाइन किया गया है, संरचना में परिवर्तन (डीडीएल) और ट्रंकट का उपयोग करना विकल्प नहीं हैं ... मुझे लगता है कि बैच हटाना सबसे अच्छा उपलब्ध है। हालांकि धन्यवाद।
tuseau

1

चूँकि यह प्रश्न एक ऐसा महत्वपूर्ण संदर्भ है जो मैं इस कोड को पोस्ट कर रहा हूँ जिससे वास्तव में मुझे लूप से हटाने में मदद मिली और प्रगति को ट्रैक करने के लिए लूप के भीतर मैसेजिंग करने में भी मदद मिली।

इस डुप्लिकेट प्रश्न से क्वेरी को संशोधित किया गया है । क्वेरी बेस के लिए @RLF को क्रेडिट ।

CREATE TABLE #DelTest (ID INT IDENTITY, name NVARCHAR(128)); -- Build the test table
INSERT INTO #DelTest (name) SELECT name FROM sys.objects;  -- fill from system DB
SELECT COUNT(*) TableNamesContainingSys FROM #deltest WHERE name LIKE '%sys%'; -- check rowcount
go
DECLARE @HowMany INT;
DECLARE @RowsTouched INT;
DECLARE @TotalRowCount INT;
DECLARE @msg VARCHAR(100);
DECLARE @starttime DATETIME 
DECLARE @currenttime DATETIME 

SET @RowsTouched = 1; -- Needs to be >0 for loop to start
SET @TotalRowCount=0  -- Total rows deleted so far is 0
SET @HowMany = 5;     -- Variable to choose how many rows to delete per loop
SET @starttime=GETDATE()

WHILE @RowsTouched > 0
BEGIN
   DELETE TOP (@HowMany)
   FROM #DelTest 
   WHERE name LIKE '%sys%';

   SET @RowsTouched = @@ROWCOUNT; -- Rows deleted this loop
   SET @TotalRowCount = @TotalRowCount+@RowsTouched; -- Increment Total rows deleted count
   SET @currenttime = GETDATE();
   SELECT @msg='Deleted ' + CONVERT(VARCHAR(9),@TotalRowCount) + ' Records. Runtime so far is '+CONVERT(VARCHAR(30),DATEDIFF(MILLISECOND,@starttime,@currenttime))+' milliseconds.'
   RAISERROR(@msg, 0, 1) WITH NOWAIT;  -- Print message after every loop. Can't use the PRINT function as SQL buffers output in loops.  

END; 
SELECT COUNT(*) TableNamesContainingSys FROM #DelTest WHERE name LIKE '%sys%'; -- Check row count after loop finish
DROP TABLE #DelTest;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.