sql server: อัพเดทฟิลด์บนโต๊ะขนาดใหญ่ในกลุ่มเล็ก ๆ : วิธีการรับความคืบหน้า / สถานะ?


10

เรามีตารางที่มีขนาดใหญ่มาก (100 ล้านแถว) และเราจำเป็นต้องอัปเดตเขตข้อมูลสองสามแห่ง

สำหรับบันทึกการจัดส่ง ฯลฯ เรายังต้องการเก็บไว้ในธุรกรรมที่มีขนาดพอดี

  • ด้านล่างจะทำเคล็ดลับหรือไม่
  • และเราจะให้มันพิมพ์ผลงานออกมาได้อย่างไรเพื่อเราจะได้เห็นความคืบหน้า? (เราพยายามเพิ่มคำสั่ง 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 Agent (เป็น 100 ล้านแถว, หลังจากทั้งหมด), ฉันไม่คิดว่ารูปแบบใด ๆ ของการส่งข้อความสถานะไปยังไคลเอนต์ (เช่น SSMS) จะเหมาะ (แม้ว่าจะเป็น เคยมีความต้องการโครงการอื่น ๆ จากนั้นฉันเห็นด้วยกับ Vladimir ว่าการใช้RAISERROR('', 10, 1) WITH NOWAIT;เป็นวิธีที่จะทำ)

ในกรณีพิเศษนี้ฉันจะสร้างตารางสถานะที่สามารถอัปเดตต่อลูปแต่ละครั้งด้วยจำนวนแถวที่อัปเดตแล้ว และไม่เจ็บที่จะโยนในเวลาปัจจุบันเพื่อให้หัวใจเต้นบนกระบวนการ

เนื่องจากคุณต้องการยกเลิกและรีสตาร์ทกระบวนการ ฉันเหนื่อยกับการห่อค่า UPDATE ของตารางหลักพร้อมกับ UPDATE ของตารางสถานะในธุรกรรมที่ชัดเจน อย่างไรก็ตามหากคุณรู้สึกว่าตารางสถานะไม่ซิงค์เนื่องจากการยกเลิกมันเป็นเรื่องง่ายที่จะรีเฟรชด้วยค่าปัจจุบันโดยเพียงแค่อัปเดตด้วยตนเองด้วยCOUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULLแต่ถ้าคุณรู้สึกว่าตารางสถานะที่เคยออกจากซิงค์เนื่องจากการยกเลิกมันเป็นเรื่องง่ายที่จะฟื้นฟูมีค่าในปัจจุบันโดยเพียงแค่การปรับปรุงด้วยตนเองด้วยและมีสองตารางที่จะอัปเดต (เช่นตารางหลักและตารางสถานะ) เราควรใช้ธุรกรรมที่ชัดเจนเพื่อให้ทั้งสองตารางซิงค์กัน แต่เราไม่ต้องการเสี่ยงที่จะมีธุรกรรมที่ไม่ถูกต้องหากคุณยกเลิกกระบวนการที่ หลังจากที่มันได้เริ่มทำธุรกรรม แต่ยังไม่ได้ทำมัน สิ่งนี้ควรปลอดภัยที่จะทำตราบใดที่คุณไม่หยุดงาน SQL Agent

คุณจะหยุดกระบวนการโดยไม่ได้อืมหยุดการทำงานได้อย่างไร โดยขอให้หยุด :-) อ๋อ โดยการส่งกระบวนการ "สัญญาณ" (คล้ายกับkill -3ใน Unix) คุณสามารถขอให้มันหยุดในช่วงเวลาต่อไปที่สะดวก (เช่นเมื่อไม่มีการทำธุรกรรม!) และทำความสะอาดตัวเองให้เป็นระเบียบเรียบร้อย

คุณจะสื่อสารกับกระบวนการทำงานในเซสชั่นอื่นได้อย่างไร? โดยใช้กลไกเดียวกับที่เราสร้างขึ้นเพื่อสื่อสารสถานะปัจจุบันกลับมาหาคุณ: ตารางสถานะ เราเพียงแค่ต้องเพิ่มคอลัมน์ที่กระบวนการจะตรวจสอบที่จุดเริ่มต้นของแต่ละวงเพื่อให้รู้ว่าจะดำเนินการหรือยกเลิก และเนื่องจากจุดประสงค์คือการกำหนดตารางเวลานี้เป็นงาน SQL Agent (รันทุกๆ 10 หรือ 20 นาที) เราควรตรวจสอบตั้งแต่ต้นเนื่องจากไม่มีจุดในการกรอกตาราง temp ที่มี 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 Agent หรือแม้กระทั่งใน SSMS บนคอมพิวเตอร์ของผู้อื่นหรือไม่ เพิ่งรัน:

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

ต้องการให้กระบวนการเริ่มต้นการสำรองข้อมูลอีกครั้งหรือไม่ เพิ่งรัน:

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

UPDATE:

ต่อไปนี้เป็นสิ่งเพิ่มเติมที่ควรลองซึ่งอาจปรับปรุงประสิทธิภาพของการดำเนินการนี้ ไม่มีใครรับประกันว่าจะช่วยได้ แต่อาจคุ้มค่าที่จะทำการทดสอบ และเมื่อมีการอัปเดต 100 ล้านแถวคุณจะมีเวลา / โอกาสมากมายในการทดสอบรูปแบบบางอย่าง ;-)

  1. เพิ่มTOP (@UpdateRows)ลงในแบบสอบถาม UPDATE เพื่อให้บรรทัดบนสุดมีลักษณะดังนี้:
    UPDATE TOP (@UpdateRows) ht
    บางครั้งก็ช่วยให้เครื่องมือเพิ่มประสิทธิภาพรู้ว่าจะได้รับผลกระทบจำนวนแถวสูงสุดเท่าใดจึงไม่ต้องเสียเวลาในการค้นหาอีก
  2. เพิ่มคีย์หลักลงใน#CurrentSetตารางชั่วคราว แนวคิดนี้คือการช่วยให้เครื่องมือเพิ่มประสิทธิภาพเข้าร่วมกับตาราง 100 ล้านแถว

    และเพื่อให้มีการระบุไว้เพื่อไม่ให้คลุมเครือไม่ควรมีเหตุผลใด ๆ ที่จะเพิ่ม PK ลงใน#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
มันยอดเยี่ยมมาก ฉันกำลังใช้งานอยู่ตอนนี้และมันสูบบุหรี่ที่เราสามารถเรียกใช้งานได้ในระหว่างวัน ขอบคุณ!
Jonesome Reinstate Monica

@samsmith โปรดดูหัวข้อ UPDATE ที่ฉันเพิ่งเพิ่มเนื่องจากมีแนวคิดบางอย่างที่ทำให้กระบวนการทำงานได้เร็วขึ้น
โซโลมอน Rutzky

หากไม่มีการปรับปรุง UPDATE เราจะได้รับการอัพเดทประมาณ 8 ล้านครั้ง / ชั่วโมง ... ด้วย @BatchRows ที่ตั้งไว้ที่ 10,000,000 (สิบล้าน)
Jonesome Reinstate Monica

@samsmith เยี่ยมมาก :) ใช่มั้ย โปรดทราบสองสิ่ง: 1)กระบวนการจะช้าลงเนื่องจากมีแถวน้อยลงและน้อยลงตรงกับส่วนคำสั่ง WHERE ดังนั้นทำไมจึงเป็นเวลาที่ดีในการเพิ่มดัชนีที่กรอง แต่คุณได้เพิ่มดัชนีที่ไม่ได้กรองไว้แล้วที่ เริ่มต้นดังนั้นฉันไม่แน่ใจว่าสิ่งนั้นจะช่วยหรือทำร้าย แต่ฉันก็ยังคาดหวังว่าปริมาณงานจะลดลงเมื่อใกล้ถึงความเป็นจริงแล้วและ2)คุณสามารถเพิ่มปริมาณงานได้โดยลดลงWAITFOR DELAYไปครึ่งวินาทีหรือมากกว่านั้น แต่นั่นเป็นการแลกเปลี่ยนที่เกิดขึ้นพร้อมกันและเป็นไปได้ว่าจะถูกส่งผ่านบันทึกการจัดส่งเป็นจำนวนเท่าใด
โซโลมอน Rutzky

เรามีความสุขกับ 8 ล้านแถว / ชั่วโมง ใช่เราเห็นว่ามันช้าลง เราลังเลที่จะสร้างดัชนีอีกต่อไป (เพราะตารางถูกล็อคสำหรับการสร้างทั้งหมด) สิ่งที่เราทำไปสองสามครั้งคือทำ reorg ในดัชนีที่มีอยู่เดิม (เพราะมันออนไลน์)
Jonesome Reinstate Monica

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 Standard และ 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และทิ้งเฉพาะPRINTข้อความในแท็บข้อความใน SSMS จะปรากฏเฉพาะหลังจากที่แบตช์ทั้งหมดเสร็จสิ้นหลังจาก 6 วินาที

เมื่อฉันออกความคิดเห็นPRINTและใช้RAISERRORข้อความในแท็บข้อความใน SSMS ปรากฏขึ้นโดยไม่ต้องรอ 6 วินาที แต่เป็นห่วงดำเนินไป

ที่น่าสนใจเมื่อฉันใช้ทั้งRAISERRORและPRINTฉันเห็นข้อความทั้งสอง ข้อความแรกมาจากข้อความแรกRAISERRORจากนั้นจึงหน่วงเวลา 2 วินาทีจากนั้นก่อนPRINTและวินาทีRAISERRORและต่อไป


ในกรณีอื่น ๆ ฉันใช้logตารางเฉพาะแยกต่างหากและเพียงแค่แทรกแถวลงในตารางพร้อมกับข้อมูลบางอย่างที่อธิบายถึงสถานะปัจจุบันและเวลาประทับของกระบวนการที่ใช้เวลานาน

ในขณะที่กระบวนการที่ยาวนานนั้นฉันรันSELECTจากlogตารางเป็นระยะเพื่อดูว่าเกิดอะไรขึ้น

เห็นได้ชัดว่ามีค่าใช้จ่ายบางอย่าง แต่มันออกจากบันทึก (หรือประวัติของบันทึก) ที่ฉันสามารถตรวจสอบด้วยตนเองในภายหลัง


บน SQL 2008/2014 เราไม่สามารถเห็นผลลัพธ์จาก Ra ...
Jonesome Reinstate Monica

@samsmith ฉันได้เพิ่มตัวอย่างที่สมบูรณ์ ลองมัน. คุณมีพฤติกรรมแบบไหนในตัวอย่างง่ายๆนี้
Vladimir Baranov

2

คุณสามารถตรวจสอบได้จากการเชื่อมต่ออื่นกับสิ่งที่ชอบ:

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

เพื่อดูว่าเหลืออยู่ให้ทำ สิ่งนี้อาจมีประโยชน์หากแอปพลิเคชันกำลังเรียกใช้กระบวนการแทนที่จะเรียกใช้ด้วยตนเองใน SSMS หรือที่คล้ายกันและต้องการแสดงความคืบหน้า: เรียกใช้กระบวนการหลักแบบอะซิงโครนัส (หรือในเธรดอื่น) จากนั้นวนรอบ "ตรวจสอบทุก ๆ ครั้งจนกว่าการเรียก async (หรือเธรด) จะเสร็จสิ้น

การตั้งค่าระดับการแยกเป็นหละหลวมที่สุดเท่าที่เป็นไปได้หมายความว่าสิ่งนี้ควรกลับมาในเวลาที่เหมาะสมโดยไม่ต้องติดอยู่ด้านหลังธุรกรรมหลักเนื่องจากปัญหาการล็อค อาจหมายความว่าค่าที่ส่งคืนนั้นไม่ถูกต้องแน่นอน แต่เนื่องจากตัววัดความก้าวหน้าอย่างง่าย ๆ นี้ไม่น่าจะมีความหมายอะไรเลย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.