SQL तालिका से लाखों पंक्तियाँ हटाएँ


9

मुझे 221+ मिलियन पंक्ति तालिका से 16+ लाखों रिकॉर्ड हटाना है और यह बहुत धीरे-धीरे चल रहा है।

मैं सराहना करता हूं कि क्या आप तेजी से नीचे कोड बनाने के लिए सुझाव साझा करते हैं:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

DECLARE @BATCHSIZE INT,
        @ITERATION INT,
        @TOTALROWS INT,
        @MSG VARCHAR(500);
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;

BEGIN TRY
    BEGIN TRANSACTION;

    WHILE @BATCHSIZE > 0
        BEGIN
            DELETE TOP (@BATCHSIZE) FROM MySourceTable
            OUTPUT DELETED.*
            INTO MyBackupTable
            WHERE NOT EXISTS (
                                 SELECT NULL AS Empty
                                 FROM   dbo.vendor AS v
                                 WHERE  VendorId = v.Id
                             );

            SET @BATCHSIZE = @@ROWCOUNT;
            SET @ITERATION = @ITERATION + 1;
            SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
            SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);             
            PRINT @MSG;
            COMMIT TRANSACTION;
            CHECKPOINT;
        END;
END TRY
BEGIN CATCH
    IF @@ERROR <> 0
       AND @@TRANCOUNT > 0
        BEGIN
            PRINT 'There is an error occured.  The database update failed.';
            ROLLBACK TRANSACTION;
        END;
END CATCH;
GO

निष्पादन योजना (2 पुनरावृत्तियों के लिए सीमित)

यहां छवि विवरण दर्ज करें

VendorIdहै पी और गैर क्लस्टर , जहां संकुल अनुक्रमणिका इस स्क्रिप्ट द्वारा उपयोग में नहीं है। 5 अन्य गैर-अद्वितीय, गैर-क्लस्टर इंडेक्स हैं।

टास्क "विक्रेताओं को हटा रहा है जो किसी अन्य तालिका में मौजूद नहीं हैं" और उन्हें किसी अन्य तालिका में वापस कर दें। मेरे पास 3 टेबल हैं vendors, SpecialVendors, SpecialVendorBackups,। SpecialVendorsजो Vendorsतालिका में मौजूद नहीं है, उसे हटाने की कोशिश कर रहा हूं और जो मैं कर रहा हूं उसके मामले में हटाए गए रिकॉर्ड का बैकअप लेने के लिए और मुझे उन्हें एक या दो सप्ताह में वापस लाना होगा।


मैं उस क्वेरी को अनुकूलित करने पर काम करूंगा और
paparazzo

जवाबों:


8

निष्पादन योजना से पता चलता है कि यह किसी क्रम में एक गैर-अनुक्रमित सूचकांक से पंक्तियों को पढ़ रहा है, फिर मूल्यांकन करने के लिए प्रत्येक बाहरी पंक्ति के लिए प्रदर्शन करना चाहता है। NOT EXISTS

यहां छवि विवरण दर्ज करें

आप तालिका का 7.2% हटा रहे हैं। 4,500 के 3,556 बैचों में 16,000,000 पंक्तियाँ

यह मानते हुए कि जो पंक्तियाँ योग्य हैं वे अंततः पूरे सूचकांक में वितरित की जाती हैं, तो इसका मतलब है कि यह प्रत्येक 13.8 पंक्तियों में लगभग 1 पंक्ति को हटा देगा।

तो पुनरावृति 1 62,156 पंक्तियों को पढ़ेगी और प्रदर्शन करेगी कि कई सूचकांक इसे हटाने से पहले 4,500 का पता लगाते हैं।

पुनरावृति 2 57,656 (62,156 - 4,500) पंक्तियों को पढ़ेगा, जो निश्चित रूप से किसी भी समवर्ती अद्यतन (जैसा कि वे पहले से ही संसाधित हो चुके हैं) को अनदेखा करने के लिए योग्य नहीं होंगे और फिर हटाने के लिए 4,500 पाने के लिए 62,156 पंक्तियाँ।

3 पुनरावृत्ति 3 (2 * 57,656) + 62,156 पंक्तियों को पढ़ेगी और इसी तरह से अंत में पुनरावृत्ति 3,556 (3,555 * 57,656) + 62,156 पंक्तियों को पढ़ेगी और कई शोध करेगी।

इसलिए सभी बैचों में प्रदर्शन किए गए सूचकांक की संख्या है SUM(1, 2, ..., 3554, 3555) * 57,656 + (3556 * 62156)

जो है ((3555 * 3556 / 2) * 57656) + (3556 * 62156)- या364,652,494,976

मैं आपको सुझाव दूंगा कि आप पहले टेम्‍परेचर टेबल में डिलीट करने के लिए पंक्‍तियों को मटीरियलाइज करें

INSERT INTO #MyTempTable
SELECT MySourceTable.PK,
       1 + ( ROW_NUMBER() OVER (ORDER BY MySourceTable.PK) / 4500 ) AS BatchNumber
FROM   MySourceTable
WHERE  NOT EXISTS (SELECT *
                   FROM   dbo.vendor AS v
                   WHERE  VendorId = v.Id) 

और बदल DELETEनष्ट करने के लिए WHERE PK IN (SELECT PK FROM #MyTempTable WHERE BatchNumber = @BatchNumber)आप अभी भी एक शामिल करने के लिए आवश्यकता हो सकती है NOT EXISTSमें DELETEअद्यतन के लिए क्वेरी ही पूरा करने के लिए के बाद से अस्थायी तालिका बसा हुआ था लेकिन यह बहुत अधिक कुशल के रूप में यह केवल 4,500 प्रति बैच चाहता प्रदर्शन करने के लिए की आवश्यकता होगी होना चाहिए।


जब आप कहते हैं "पहले एक टेम्‍परेचर टेबल में डिलीट करने के लिए पंक्तियों को मटेरिअल करें" तो क्या आप उन सभी रिकॉर्ड्स को अपने सभी कॉलमों को टेम्प टेबल में रखने का सुझाव दे रहे हैं? या केवल PKकॉलम? (मुझे विश्वास है कि आप मुझे उन लोगों को पूरी तरह से अस्थायी तालिका में ले जाने का सुझाव दे रहे हैं, लेकिन दोहरी जाँच करना चाहते हैं)
साइलर

@ साइलर - बस प्रमुख कॉलम (ओं)
मार्टिन स्मिथ

आप जल्दी से समीक्षा कर सकते हैं इस करता है, तो मैं क्या आप सही तरीके से या नहीं कहा, कृपया मिल सकता है?
सिलचर

@ साइकलर - DELETE TOP (@BATCHSIZE) FROM MySourceTableबस टेम्पर DELETE FROM MySourceTable टेबल को भी इंडेक्स किया जाना चाहिए CREATE TABLE #MyTempTable ( Id BIGINT, BatchNumber BIGINT, PRIMARY KEY(BatchNumber, Id) );और VendorIdनिश्चित रूप से अपने आप ही पीके है? आपके पास 221 मिलियन विभिन्न विक्रेता हैं?
मार्टिन स्मिथ

धन्यवाद मार्टिन, 6:00 के बाद इसका परीक्षण करेंगे। और आपका जवाब है, यह निश्चित रूप से उस तालिका में एकमात्र पीके मौजूद है
साइलर

4

निष्पादन योजना बताती है कि प्रत्येक लूप लूप पिछले लूप की तुलना में अधिक काम करेगा। यह मानते हुए कि हटाने के लिए पंक्तियाँ समान रूप से तालिका में वितरित की जाती हैं पहला लूप हटाने के लिए 4500 पंक्तियों को खोजने के लिए लगभग 4500 * 221000000/16000000 = 62156 पंक्तियों को स्कैन करना होगा। यह vendorटेबल के मुकाबले समान संख्या वाले क्लस्टर इंडेक्स का भी प्रयास करेगा । हालांकि, दूसरे लूप को उसी 62156 - 4500 = 57656 पंक्तियों को पढ़ने की आवश्यकता होगी जिसे आपने पहली बार नहीं हटाया था। हम उम्मीद कर सकते हैं कि दूसरे लूप से 120000 पंक्तियों को स्कैन किया MySourceTableजा सकता है और vendorतालिका के खिलाफ 120000 की तलाश की जा सकती है। प्रति लूप में आवश्यक कार्य की मात्रा एक रैखिक दर से बढ़ जाती है। एक सन्निकटन के रूप में हम कह सकते हैं कि औसत लूप को 102516868 पंक्तियों को पढ़ना होगा MySourceTableऔर 102516868 पंक्तियों के खिलाफ करना होगाvendorतालिका। 4500 के बैच आकार वाली 16 मिलियन पंक्तियों को हटाने के लिए आपके कोड को 16000000/4500 = 3556 लूप करने की आवश्यकता होती है, इसलिए आपके कोड को पूरा करने के लिए काम की कुल राशि लगभग 364.5 बिलियन पंक्तियों से पढ़ी जाती है MySourceTableऔर 364.5 बिलियन इंडेक्स की तलाश है।

एक छोटी समस्या यह है कि आप @BATCHSIZEकिसी RECOMPILEया कुछ अन्य संकेत के बिना एक शीर्ष अभिव्यक्ति में एक स्थानीय चर का उपयोग करते हैं। योजना बनाते समय क्वेरी ऑप्टिमाइज़र को उस स्थानीय चर का मूल्य नहीं पता होगा। यह मान लेगा कि यह 100 के बराबर है। वास्तव में आप 100 के बजाय 4500 पंक्तियों को हटा रहे हैं, और आप संभवतः उस विसंगति के कारण कम कुशल योजना के साथ समाप्त हो सकते हैं। तालिका में सम्मिलित करते समय कम कार्डिनिटी का अनुमान प्रदर्शन के कारण भी मारा जा सकता है। SQL सर्वर आवेषण करने के लिए एक अलग आंतरिक एपीआई चुन सकता है अगर उसे लगता है कि उसे 4500 पंक्तियों के विपरीत 100 पंक्तियों को सम्मिलित करने की आवश्यकता है।

एक विकल्प केवल उन पंक्तियों की प्राथमिक कुंजियों / संकुल कुंजी को सम्मिलित करना है जिन्हें आप एक अस्थायी तालिका में हटाना चाहते हैं। आपके प्रमुख स्तंभों के आकार के आधार पर यह आसानी से tempdb में फिट हो सकता है। आप उस मामले में न्यूनतम लॉगिंग प्राप्त कर सकते हैं जिसका अर्थ है कि लेन-देन लॉग नहीं उड़ाएगा। आप पुनर्प्राप्ति मॉडल के साथ किसी भी डेटाबेस के खिलाफ न्यूनतम लॉगिंग प्राप्त कर सकते हैं SIMPLE। आवश्यकताओं के बारे में अधिक जानकारी के लिए लिंक देखें।

यदि वह विकल्प नहीं है, तो आपको अपना कोड बदलना चाहिए ताकि आप उस पर मौजूद सूचकांक का लाभ उठा सकें MySourceTable। महत्वपूर्ण बात यह है कि आप अपने कोड को लिखें ताकि आप प्रति लूप लगभग समान काम करें। आप हर बार शुरुआत से ही टेबल को स्कैन करने के बजाय इंडेक्स का लाभ उठाकर ऐसा कर सकते हैं। मैंने एक ब्लॉग पोस्ट लिखी है जो लूपिंग के कुछ अलग तरीकों पर जाती है। उस पोस्ट के उदाहरण हटाए जाने के बजाय एक तालिका में सम्मिलित होते हैं लेकिन आपको कोड को अनुकूलित करने में सक्षम होना चाहिए।

नीचे दिए गए उदाहरण कोड में मुझे लगता है कि प्राथमिक कुंजी और आपके की क्लस्टर कुंजी MySourceTable। मैंने यह कोड बहुत जल्दी लिखा है और मैं इसका परीक्षण करने में सक्षम नहीं हूं:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

DECLARE @BATCHSIZE INT,
        @ITERATION INT,
        @TOTALROWS INT,
        @MSG VARCHAR(500)
        @STARTID BIGINT,
        @NEXTID BIGINT;
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;

SELECT @STARTID = ID
FROM MySourceTable
ORDER BY ID
OFFSET 0 ROWS
FETCH FIRST 1 ROW ONLY;

SELECT @NEXTID = ID
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
OFFSET (60000) ROWS
FETCH FIRST 1 ROW ONLY;

BEGIN TRY
    BEGIN TRANSACTION;

    WHILE @STARTID IS NOT NULL
        BEGIN
            WITH MySourceTable_DELCTE AS (
                SELECT TOP (60000) *
                FROM MySourceTable
                WHERE ID >= @STARTID
                ORDER BY ID
            )           
            DELETE FROM MySourceTable_DELCTE
            OUTPUT DELETED.*
            INTO MyBackupTable
            WHERE NOT EXISTS (
                                 SELECT NULL AS Empty
                                 FROM   dbo.vendor AS v
                                 WHERE  VendorId = v.Id
                             );

            SET @BATCHSIZE = @@ROWCOUNT;
            SET @ITERATION = @ITERATION + 1;
            SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
            SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);             
            PRINT @MSG;
            COMMIT TRANSACTION;

            CHECKPOINT;

            SET @STARTID = @NEXTID;
            SET @NEXTID = NULL;

            SELECT @NEXTID = ID
            FROM MySourceTable
            WHERE ID >= @STARTID
            ORDER BY ID
            OFFSET (60000) ROWS
            FETCH FIRST 1 ROW ONLY;

        END;
END TRY
BEGIN CATCH
    IF @@ERROR <> 0
       AND @@TRANCOUNT > 0
        BEGIN
            PRINT 'There is an error occured.  The database update failed.';
            ROLLBACK TRANSACTION;
        END;
END CATCH;
GO

मुख्य भाग यहाँ है:

WITH MySourceTable_DELCTE AS (
    SELECT TOP (60000) *
    FROM MySourceTable
    WHERE ID >= @STARTID
    ORDER BY ID
)   

प्रत्येक लूप केवल 60000 पंक्तियों को पढ़ेगा MySourceTable। जिसके परिणामस्वरूप प्रति लेनदेन औसतन 4500 पंक्तियों का औसत आकार और प्रति लेनदेन 60000 पंक्तियों का अधिकतम हटाना आकार होना चाहिए। यदि आप एक छोटे बैच आकार के साथ अधिक रूढ़िवादी होना चाहते हैं जो ठीक भी है। @STARTIDप्रत्येक पाश के बाद चर अग्रिमों आप स्रोत तालिका से एक बार से अधिक एक ही पंक्ति को पढ़ने से बचने कर सकते हैं।


विस्तृत जानकारी के लिए धन्यवाद। मैंने सेट किया कि 4500 की सीमा टेबल को लॉक नहीं करने के लिए। अगर मैं गलत नहीं हूँ तो SQL की एक हार्ड सीमा है जो कि यदि डिलीट काउंट 5000 से ऊपर जाती है तो पूरे टेबल को लॉक कर देती है। और चूंकि यह एक लंबी प्रक्रिया होगी, इसलिए मैं उस टेबल को लंबे समय तक लॉक करने का प्रयास नहीं कर सकता। अगर मैं उस 60000 से 4500 पर सेट करता हूं, तो क्या आपको लगता है कि मुझे वही प्रदर्शन मिलेगा?
साइलर

यदि आप लॉक एस्केलेशन के बारे में चिंतित हैं तो आप इसे टेबल स्तर पर निष्क्रिय कर सकते हैं। 4500 के बैच आकार का उपयोग करने में कुछ भी गलत नहीं है। कुंजी यह है कि प्रत्येक लूप लगभग समान रूप से काम करेगा।
जो ओबिश

मुझे गति के अंतर के कारण अन्य उत्तर स्वीकार करने होंगे। मैंने आपके समाधान का परीक्षण किया और @ मार्टिन-स्मिथ के समाधान और उनके संस्करण में 10 मिनट के परीक्षण के लिए अधिक डेटा ~ 2% मिल रहा है। आपके समाधान मेरी तुलना में बहुत बेहतर हैं और मैं वास्तव में आपके समय के लिए सराहना करता हूं ... -
cilerler

2

मन में दो विचार वसंत:

डेटा की उस मात्रा के साथ अनुक्रमण के कारण देरी हो सकती है। अनुक्रमणिका को छोड़ने, हटाने, और अनुक्रमणिका पुन: बनाने का प्रयास करें।

या ..

यह उन पंक्तियों की प्रतिलिपि बनाने के लिए तेज़ हो सकता है जिन्हें आप एक अस्थायी तालिका में रखना चाहते हैं, तालिका को 16 मिलियन पंक्तियों के साथ छोड़ दें, और अस्थायी तालिका का नाम बदलें (या स्रोत तालिका के नए उदाहरण पर प्रतिलिपि बनाएं)।

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