ฉันไม่ทราบคำถามนี้เมื่อฉันตอบคำถามที่เกี่ยวข้อง ( จำเป็นต้องทำธุรกรรมอย่างชัดเจนในขณะนี้หรือไม่ ) แต่เพื่อความสมบูรณ์ฉันจะแก้ไขปัญหานี้ที่นี่เนื่องจากไม่ใช่ส่วนหนึ่งของคำแนะนำในคำตอบที่เชื่อมโยงนั้น .
เนื่องจากฉันแนะนำให้กำหนดตารางเวลานี้ผ่านงาน 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 ล้านแถวคุณจะมีเวลา / โอกาสมากมายในการทดสอบรูปแบบบางอย่าง ;-)
- เพิ่ม
TOP (@UpdateRows)
ลงในแบบสอบถาม UPDATE เพื่อให้บรรทัดบนสุดมีลักษณะดังนี้:
UPDATE TOP (@UpdateRows) ht
บางครั้งก็ช่วยให้เครื่องมือเพิ่มประสิทธิภาพรู้ว่าจะได้รับผลกระทบจำนวนแถวสูงสุดเท่าใดจึงไม่ต้องเสียเวลาในการค้นหาอีก
เพิ่มคีย์หลักลงใน#CurrentSet
ตารางชั่วคราว แนวคิดนี้คือการช่วยให้เครื่องมือเพิ่มประสิทธิภาพเข้าร่วมกับตาราง 100 ล้านแถว
และเพื่อให้มีการระบุไว้เพื่อไม่ให้คลุมเครือไม่ควรมีเหตุผลใด ๆ ที่จะเพิ่ม PK ลงใน#FullSet
ตารางชั่วคราวเนื่องจากเป็นเพียงตารางคิวอย่างง่าย ๆ ซึ่งคำสั่งนั้นไม่เกี่ยวข้อง
- ในบางกรณีจะช่วยเพิ่มดัชนีตัวกรองเพื่อช่วยให้
SELECT
ฟีดนั้นลงใน#FullSet
ตารางอุณหภูมิ ต่อไปนี้เป็นข้อควรพิจารณาบางประการเกี่ยวกับการเพิ่มดัชนีดังกล่าว:
- เงื่อนไข WHERE ควรตรงกับเงื่อนไข WHERE ของข้อความค้นหาของคุณ
WHERE deleted is null or deletedDate is null
- ที่จุดเริ่มต้นของกระบวนการแถวส่วนใหญ่จะตรงกับเงื่อนไข WHERE ของคุณดังนั้นดัชนีไม่เป็นประโยชน์ คุณอาจต้องการรอจนกว่าจะมีเครื่องหมายประมาณ 50% ก่อนที่จะเพิ่มสิ่งนี้ แน่นอนว่าจะช่วยได้มากแค่ไหนและเมื่อใดควรเพิ่มดัชนีให้แตกต่างกันไปเนื่องจากปัจจัยหลายประการดังนั้นจึงเป็นการลองผิดลองถูก
- คุณอาจต้องอัปเดตสถานะด้วยตนเองและ / หรือสร้างดัชนีใหม่เพื่อให้เป็นปัจจุบันอยู่เสมอเนื่องจากข้อมูลฐานมีการเปลี่ยนแปลงค่อนข้างบ่อย
- โปรดจำไว้ว่าดัชนีในขณะที่ช่วยเหลือ
SELECT
นั้นจะทำให้เกิดความเสียหายUPDATE
เนื่องจากเป็นวัตถุอื่นที่ต้องได้รับการอัปเดตระหว่างการดำเนินการนั้นด้วยเหตุนี้ I / O มากขึ้น สิ่งนี้เล่นได้ทั้งสองแบบโดยใช้ดัชนีตัวกรอง (ซึ่งย่อขนาดเมื่อคุณอัปเดตแถวเนื่องจากแถวที่ตรงกับตัวกรองน้อยลง) และรอสักครู่เพื่อเพิ่มดัชนี I / O เพิ่มเติม)