एसक्यूएल सर्वर: छोटे चंक्स में विशाल टेबल पर फ़ील्ड अपडेट करना: प्रगति / स्थिति कैसे प्राप्त करें?


10

हमारे पास एक बहुत बड़ी (100million पंक्ति) तालिका है, और हमें उस पर कुछ फ़ील्ड अपडेट करने की आवश्यकता है।

लॉग शिपिंग, आदि के लिए, हम भी, जाहिर है, इसे काटने के आकार के लेनदेन के लिए रखना चाहते हैं।

  • क्या नीचे ट्रिक करेगा?
  • और हम इसे कुछ आउटपुट प्रिंट करने के लिए कैसे प्राप्त कर सकते हैं, इसलिए हम प्रगति देख सकते हैं? (हमने वहां एक PRINT स्टेटमेंट जोड़ने की कोशिश की, लेकिन लूप के दौरान कुछ भी आउटपुट नहीं हुआ)

कोड है:

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END

जवाबों:


12

मुझे इस प्रश्न के बारे में पता नहीं था जब मैंने संबंधित प्रश्न का उत्तर दिया ( लूप में इसमें स्पष्ट लेनदेन आवश्यक हैं? ), लेकिन पूर्णता के लिए, मैं इस मुद्दे को यहां संबोधित करूंगा क्योंकि यह उस लिंक किए गए उत्तर में मेरे सुझाव का हिस्सा नहीं था ।

चूँकि मैं SQL एजेंट जॉब के माध्यम से इसे शेड्यूल करने का सुझाव दे रहा हूँ (यह 100 मिलियन पंक्तियाँ हैं, आखिरकार), मुझे नहीं लगता कि क्लाइंट (यानी SSMS) को स्टेटस मैसेज भेजने का कोई भी रूप आदर्श होगा (हालांकि अगर ऐसा है तो) कभी अन्य परियोजनाओं की आवश्यकता होती है, तो मैं व्लादिमीर के साथ सहमत हूं कि उपयोग करने RAISERROR('', 10, 1) WITH NOWAIT;का तरीका है)।

इस विशेष मामले में, मैं एक स्टेटस टेबल बनाऊंगा जिसे प्रत्येक लूप के अनुसार अपडेट किया जा सकता है और अब तक अपडेट की गई पंक्तियों की संख्या। और यह मौजूदा समय में फेंकने के लिए प्रक्रिया पर दिल की धड़कन को चोट नहीं पहुंचाता है।

यह देखते हुए कि आप प्रक्रिया को रद्द करना और पुनः आरंभ करना चाहते हैं, मैं एक स्पष्ट लेनदेन में स्थिति तालिका के अद्यतन के साथ मुख्य तालिका के UPDATE को लपेटने से थका हुआ हूं। हालाँकि, यदि आपको लगता है कि रद्द करने के कारण स्थिति तालिका कभी भी सिंक से बाहर है, तो बस इसे मैन्युअल रूप से अपडेट करके वर्तमान मूल्य के साथ ताज़ा करना आसान है COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULLऔर अद्यतन करने के लिए दो टेबल हैं (यानी मुख्य तालिका और स्थिति तालिका), हमें उन दो तालिकाओं को सिंक में रखने के लिए एक स्पष्ट लेनदेन का उपयोग करना चाहिए , फिर भी यदि आप प्रक्रिया को रद्द करते हैं तो हम अनाथ लेनदेन का जोखिम नहीं उठाना चाहते हैं बिंदु के बाद यह लेनदेन शुरू कर दिया है, लेकिन यह प्रतिबद्ध नहीं है। यह तब तक करने के लिए सुरक्षित होना चाहिए जब तक आप SQL एजेंट काम को बंद नहीं करते।

आप um के बिना प्रक्रिया को कैसे रोक सकते हैं, ठीक है, इसे रोक सकते हैं? इसे रोकने के लिए :-) हां। प्रक्रिया को एक "सिग्नल" ( kill -3यूनिक्स में समान ) भेजकर , आप अनुरोध कर सकते हैं कि यह अगले सुविधाजनक क्षण में बंद हो जाए (यानी जब कोई सक्रिय लेनदेन न हो!) और इसे सभी अच्छे और सुव्यवस्थित तरह से साफ करें।

आप दूसरे सत्र में चल रही प्रक्रिया के साथ कैसे संवाद कर सकते हैं? उसी तंत्र का उपयोग करके जो हमने इसके लिए बनाया है ताकि आप इसकी वर्तमान स्थिति को वापस आप तक पहुंचा सकें: स्थिति तालिका। हमें बस एक कॉलम जोड़ने की आवश्यकता है जो प्रक्रिया प्रत्येक लूप की शुरुआत में जांच करेगी ताकि यह पता चले कि आगे बढ़ना है या गर्भपात करना है। और चूंकि आशय इसे SQL एजेंट की नौकरी के रूप में शेड्यूल करना है (हर 10 या 20 मिनट पर रन करें), हमें शुरुआत में ही जांच कर लेनी चाहिए क्योंकि अगर प्रक्रिया अभी चल रही है तो 1 मिलियन पंक्तियों के साथ एक अस्थायी तालिका भरने का कोई मतलब नहीं है। एक पल बाद बाहर निकलने के लिए और उस डेटा का कोई उपयोग नहीं करने के लिए।

DECLARE @BatchRows INT = 1000000,
        @UpdateRows INT = 4995;

IF (OBJECT_ID(N'dbo.HugeTable_TempStatus') IS NULL)
BEGIN
  CREATE TABLE dbo.HugeTable_TempStatus
  (
    RowsUpdated INT NOT NULL, -- updated by the process
    LastUpdatedOn DATETIME NOT NULL, -- updated by the process
    PauseProcess BIT NOT NULL -- read by the process
  );

  INSERT INTO dbo.HugeTable_TempStatus (RowsUpdated, LastUpdatedOn, PauseProcess)
  VALUES (0, GETDATE(), 0);
END;

-- First check to see if we should run. If no, don't waste time filling temp table
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
  PRINT 'Process is paused. No need to start.';
  RETURN;
END;

CREATE TABLE #FullSet (KeyField1 DataType1, KeyField2 DataType2);
CREATE TABLE #CurrentSet (KeyField1 DataType1, KeyField2 DataType2);

INSERT INTO #FullSet (KeyField1, KeyField2)
  SELECT TOP (@BatchRows) ht.KeyField1, ht.KeyField2
  FROM   dbo.HugeTable ht
  WHERE  ht.deleted IS NULL
  OR     ht.deletedDate IS NULL

WHILE (1 = 1)
BEGIN
  -- Check if process is paused. If yes, just exit cleanly.
  IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
  BEGIN
    PRINT 'Process is paused. Exiting.';
    BREAK;
  END;

  -- grab a set of rows to update
  DELETE TOP (@UpdateRows)
  FROM   #FullSet
  OUTPUT Deleted.KeyField1, Deleted.KeyField2
  INTO   #CurrentSet (KeyField1, KeyField2);

  IF (@@ROWCOUNT = 0)
  BEGIN
    RAISERROR(N'All rows have been updated!!', 16, 1);
    BREAK;
  END;

  BEGIN TRY
    BEGIN TRAN;

    -- do the update of the main table
    UPDATE ht
    SET    ht.deleted = 0,
           ht.deletedDate = '2000-01-01'
    FROM   dbo.HugeTable ht
    INNER JOIN #CurrentSet cs
            ON cs.KeyField1 = ht.KeyField1
           AND cs.KeyField2 = ht.KeyField2;

    -- update the current status
    UPDATE ts
    SET    ts.RowsUpdated += @@ROWCOUNT,
           ts.LastUpdatedOn = GETDATE()
    FROM   dbo.HugeTable_TempStatus ts;

    COMMIT TRAN;
  END TRY
  BEGIN CATCH
    IF (@@TRANCOUNT > 0)
    BEGIN
      ROLLBACK TRAN;
    END;

    THROW; -- raise the error and terminate the process
  END CATCH;

  -- clear out rows to update for next iteration
  TRUNCATE TABLE #CurrentSet;

  WAITFOR DELAY '00:00:01'; -- 1 second delay for some breathing room
END;

-- clean up temp tables when testing
-- DROP TABLE #FullSet; 
-- DROP TABLE #CurrentSet; 

आप निम्न क्वेरी का उपयोग करके किसी भी समय स्थिति की जांच कर सकते हैं:

SELECT sp.[rows] AS [TotalRowsInTable],
       ts.RowsUpdated,
       (sp.[rows] - ts.RowsUpdated) AS [RowsRemaining],
       ts.LastUpdatedOn
FROM sys.partitions sp
CROSS JOIN dbo.HugeTable_TempStatus ts
WHERE  sp.[object_id] = OBJECT_ID(N'ResizeTest')
AND    sp.[index_id] < 2;

प्रक्रिया को विराम देना चाहते हैं, चाहे वह SQL एजेंट नौकरी में चल रहा हो या SSMS में किसी और के कंप्यूटर पर भी? बस दौडो:

UPDATE ht
SET    ht.PauseProcess = 1
FROM   dbo.HugeTable_TempStatus ts;

क्या प्रक्रिया फिर से शुरू करने में सक्षम होना चाहते हैं? बस दौडो:

UPDATE ht
SET    ht.PauseProcess = 0
FROM   dbo.HugeTable_TempStatus ts;

अपडेट करें:

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

  1. TOP (@UpdateRows)UPDATE क्वेरी में जोड़ें , ताकि शीर्ष पंक्ति दिखे:
    UPDATE TOP (@UpdateRows) ht
    कभी-कभी यह ऑप्टिमाइज़र को यह जानने में मदद करता है कि अधिकतम कितनी पंक्तियाँ प्रभावित होंगी ताकि यह अधिक समय की तलाश में बर्बाद न हो।
  2. #CurrentSetअस्थायी तालिका में एक प्राथमिक कुंजी जोड़ें । यहाँ विचार 100 मिलियन रो टेबल के लिए JOIN के साथ ऑप्टिमाइज़र की मदद करना है।

    और बस यह कहा गया है ताकि अस्पष्ट न हो, #FullSetअस्थायी तालिका में पीके को जोड़ने का कोई कारण नहीं होना चाहिए क्योंकि यह सिर्फ एक सरल कतार तालिका है जहां आदेश अप्रासंगिक है।

  3. कुछ मामलों में यह टेम्परिंग इंडेक्स को जोड़ने में मदद करता है ताकि टेम्प टेबल SELECTमें फीड हो सके #FullSet। इस तरह के सूचकांक को जोड़ने से संबंधित कुछ विचार इस प्रकार हैं:
    1. WHERE की स्थिति को आपकी क्वेरी की WHERE की स्थिति से मेल खाना चाहिए, इसलिए WHERE deleted is null or deletedDate is null
    2. प्रक्रिया की शुरुआत में, अधिकांश पंक्तियाँ आपके WHERE की स्थिति से मेल खाएँगी, इसलिए कोई अनुक्रमणिका सहायक नहीं है। इसे जोड़ने से पहले आप 50% के आसपास कहीं इंतजार करना चाहते हैं। बेशक, यह कितना मदद करता है और जब सूचकांक को जोड़ना सबसे अच्छा होता है तो कई कारकों के कारण अलग-अलग होते हैं, इसलिए यह थोड़ा परीक्षण और त्रुटि है।
    3. आपको बेस डेटा को बार-बार बदल रहा है क्योंकि आपको इसे अपडेट रखने के लिए मैन्युअल रूप से अपडेट और / या रिब्यूल्ड करना पड़ सकता है।
    4. यह ध्यान रखें कि मदद करते समय सूचकांक SELECTको चोट लगेगी, UPDATEक्योंकि यह एक अन्य ऑब्जेक्ट है जो उस ऑपरेशन के दौरान अपडेट होना चाहिए, इसलिए अधिक I / O। यह फ़िल्टर किए गए इंडेक्स का उपयोग करके दोनों में खेलता है (जो कि कम पंक्तियों को फ़िल्टर से मेल खाने के बाद से आप अपडेट करते हैं), और इंडेक्स को जोड़ने के लिए थोड़ी प्रतीक्षा कर रहे हैं (यदि यह शुरुआत में सुपर सहायक नहीं हो रहा है, तो कोई कारण नहीं है अतिरिक्त I / O)।

1
यह उत्कृष्ट है। मैं इसे अभी चला रहा हूं, और यह धूम्रपान करता है कि हम इसे दिन के दौरान लाइन पर चला सकते हैं। धन्यवाद!
जोन्सोमे मोनिका सेप

@samsmith कृपया UPDATE अनुभाग देखें मैंने अभी जोड़ा है क्योंकि संभावित रूप से प्रक्रिया को और भी तेज़ करने के लिए कुछ विचार हैं।
सोलोमन रटज़की

UPDATE संवर्द्धन के बिना, हम लगभग 8 मिलियन अपडेट / घंटा प्राप्त कर रहे हैं ... @BatchRows के साथ 10000000 (दस मिलियन) पर सेट
जोन्सोम

@samsmith यह महान है :) सही? दो बातों का ध्यान रखें: 1) प्रक्रिया धीमी हो जाएगी क्योंकि WHERE क्लॉज से मेल खाते कम और कम पंक्तियाँ हैं, इसलिए फ़िल्टर किए गए इंडेक्स को जोड़ने के लिए एक अच्छा समय क्यों होगा, लेकिन आपने पहले ही एक गैर-फ़िल्टर किए गए इंडेक्स को जोड़ दिया था शुरू करो तो मुझे यकीन नहीं है कि इससे मदद मिलेगी या चोट लगेगी, लेकिन फिर भी मैं उम्मीद करूंगा कि थ्रूपुट कम हो जाएगा क्योंकि यह किया जा रहा है, और 2) आप एक-दूसरे को कम करके या थ्रूपुट को बढ़ा सकते हैंWAITFOR DELAY । लेकिन यह एक व्यापार-संगति है और संभवत: लॉग-शिपिंग के माध्यम से कितना भेजा जाता है।
सोलोमन रटज़की

हम 8 मिलियन पंक्तियों / घंटे से खुश हैं। हां, हम इसे धीमा देख सकते हैं। हम किसी भी अधिक अनुक्रमित को बनाने में संकोच कर रहे हैं (क्योंकि तालिका पूरे निर्माण के लिए बंद है)। हमने जो कुछ किया है वह मौजूदा इंडेक्स (क्योंकि वह लाइन पर है) पर एक रीगॉर करते हैं।
जोंसोम ने मोनिका को

4

दूसरे भाग का उत्तर देना: लूप के दौरान कुछ आउटपुट कैसे प्रिंट करें।

मेरे पास कुछ लंबे समय तक चलने वाली रखरखाव प्रक्रियाएं हैं जो sys व्यवस्थापक को कभी-कभी चलाना पड़ता है।

मैं उन्हें SSMS से चलाता हूं और यह भी देखा कि PRINTपूरी प्रक्रिया पूरी होने के बाद ही SSMS में स्टेटमेंट दिखाया गया है।

इसलिए, मैं RAISERRORकम गंभीरता के साथ उपयोग कर रहा हूं :

DECLARE @VarTemp nvarchar(32);
SET @VarTemp = CONVERT(nvarchar(32), GETDATE(), 121);
RAISERROR (N'Your message. Current time is %s.', 0, 1, @VarTemp) WITH NOWAIT;

मैं SQL Server 2008 मानक और SSMS 2012 (11.0.3128.0) का उपयोग कर रहा हूं। SSMS में चलने के लिए एक पूर्ण कार्य उदाहरण यहाँ दिया गया है:

DECLARE @VarCount int = 0;
DECLARE @VarTemp nvarchar(32);

WHILE @VarCount < 3
BEGIN
    SET @VarTemp = CONVERT(nvarchar(32), GETDATE(), 121);
    --RAISERROR (N'Your message. Current time is %s.', 0, 1, @VarTemp) WITH NOWAIT;
    --PRINT @VarTemp;

    WAITFOR DELAY '00:00:02';
    SET @VarCount = @VarCount + 1;
END

जब मैं टिप्पणी करता हूं RAISERRORऔर केवल PRINTSSMS में संदेश टैब में संदेश छोड़ता हूं, तो केवल 6 सेकंड के बाद, पूरे बैच के समाप्त होने के बाद ही दिखाई देता है।

जब मैं SSMS में संदेश टैब में संदेशों को लिखता हूं PRINTऔर RAISERRORउनका उपयोग करता हूं , तो 6 सेकंड तक इंतजार किए बिना दिखाई देता है, लेकिन जैसे-जैसे लूप आगे बढ़ता है।

दिलचस्प है, जब मैं दोनों का उपयोग करता हूं RAISERRORऔर PRINT, मैं दोनों संदेश देखता हूं। पहले संदेश आता है पहले RAISERROR, फिर 2 सेकंड के लिए देरी, फिर पहले PRINTऔर दूसरे RAISERROR, और इसी तरह।


अन्य मामलों में मैं एक अलग समर्पित logतालिका का उपयोग करता हूं और बस वर्तमान स्थिति और लंबे समय से चलने वाली प्रक्रिया के टाइमस्टैम्प का वर्णन करने वाली कुछ जानकारी के साथ तालिका में एक पंक्ति सम्मिलित करता हूं ।

जबकि लंबी प्रक्रिया समय-समय SELECTपर logतालिका से चलती है कि क्या चल रहा है।

यह स्पष्ट रूप से कुछ ओवरहेड है, लेकिन यह एक लॉग (या लॉग का इतिहास) छोड़ देता है जिसे मैं बाद में अपनी गति से जांच कर सकता हूं।


SQL 2008/2014 पर, हम परिणाम बढ़ाने वाले से नहीं देख सकते हैं .... हम क्या याद कर रहे हैं?
मोनोमे

@samsmith, मैंने एक पूर्ण उदाहरण जोड़ा। कोशिश करो। इस सरल उदाहरण में आपको क्या व्यवहार मिलता है?
व्लादिमीर बारानोव

2

आप इसे किसी अन्य कनेक्शन से मॉनिटर कर सकते हैं जैसे:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT COUNT(*) FROM [huge-table] WHERE deleted IS NULL OR deletedDate IS NULL 

यह देखने के लिए कि कितना कुछ करना बाकी है। यह तब उपयोगी हो सकता है जब कोई एप्लिकेशन SSMS या समान रूप से मैन्युअल रूप से चलाने के बजाय प्रक्रिया को कॉल कर रहा हो, और प्रगति दिखाने की आवश्यकता हो: मुख्य प्रक्रिया को अतुल्यकालिक रूप से (या किसी अन्य थ्रेड पर) चलाएँ और फिर लूप कॉलिंग "कितना शेष है" "तब तक हर बार जांच करें जब तक कि async कॉल (या धागा) पूरा न हो जाए।

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

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