Tôi không nhận thức được câu hỏi này khi tôi trả lời câu hỏi liên quan ( Có cần giao dịch rõ ràng trong vòng lặp while này không? ), Nhưng để hoàn thiện, tôi sẽ giải quyết vấn đề này ở đây vì đây không phải là một phần trong gợi ý của tôi trong câu trả lời được liên kết đó .
Vì tôi đề nghị lên lịch cho công việc này thông qua công việc Tác nhân SQL (rốt cuộc là 100 triệu hàng), tôi không nghĩ rằng bất kỳ hình thức gửi thông điệp trạng thái nào đến máy khách (ví dụ: SSMS) sẽ lý tưởng (mặc dù đó là Bao giờ cần một dự án khác, thì tôi đồng ý với Vladimir rằng sử dụng RAISERROR('', 10, 1) WITH NOWAIT;
là con đường để đi).
Trong trường hợp cụ thể này, tôi sẽ tạo một bảng trạng thái có thể được cập nhật trên mỗi vòng lặp với số lượng hàng được cập nhật cho đến nay. Và nó không đau để ném vào thời điểm hiện tại để có một nhịp tim trong quá trình.
Cho rằng bạn muốn có thể hủy bỏ và khởi động lại quá trình, Tôi mệt mỏi trong việc gói CẬP NHẬT của bảng chính với CẬP NHẬT của bảng trạng thái trong một giao dịch rõ ràng. Tuy nhiên, nếu bạn cảm thấy bảng trạng thái không đồng bộ do hủy, bạn có thể dễ dàng làm mới với giá trị hiện tại bằng cách cập nhật thủ công bằng cách COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
.và có hai bảng để CẬP NHẬT (tức là bảng chính và bảng trạng thái), chúng ta nên sử dụng một giao dịch rõ ràng để giữ cho hai bảng đó được đồng bộ hóa, nhưng chúng tôi không muốn mạo hiểm khi có một giao dịch mồ côi nếu bạn hủy quá trình tại điểm sau khi nó đã bắt đầu giao dịch nhưng chưa cam kết. Điều này sẽ an toàn để làm miễn là bạn không dừng công việc Tác nhân SQL.
Làm thế nào bạn có thể dừng quá trình mà không, ừm, dừng lại? Bằng cách yêu cầu nó dừng lại :-). Vâng. Bằng cách gửi "tín hiệu" quá trình (tương tự như kill -3
trong Unix), bạn có thể yêu cầu nó dừng lại vào thời điểm thuận tiện tiếp theo (nghĩa là khi không có giao dịch hoạt động!) Và làm cho nó sạch hoàn toàn giống như gọn gàng.
Làm thế nào bạn có thể giao tiếp với quá trình đang chạy trong một phiên khác? Bằng cách sử dụng cùng một cơ chế mà chúng tôi đã tạo cho nó để truyền lại trạng thái hiện tại của nó cho bạn: bảng trạng thái. Chúng ta chỉ cần thêm một cột mà quá trình sẽ kiểm tra ở đầu mỗi vòng lặp để nó biết nên tiến hành hay hủy bỏ. Và vì mục đích là lên lịch cho công việc này như một công việc của Tác nhân SQL (chạy cứ sau 10 hoặc 20 phút), chúng tôi cũng nên kiểm tra ngay từ đầu vì không có điểm nào trong việc điền vào bảng tạm thời với 1 triệu hàng nếu quá trình chỉ diễn ra để thoát một lát sau và không sử dụng bất kỳ dữ liệu nào.
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;
Sau đó, bạn có thể kiểm tra trạng thái bất cứ lúc nào bằng truy vấn sau:
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;
Bạn muốn tạm dừng quá trình, cho dù nó đang chạy trong công việc Tác nhân SQL hoặc thậm chí trong SSMS trên máy tính của người khác? Chỉ cần chạy:
UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
Bạn muốn quá trình có thể bắt đầu lại từ đầu? Chỉ cần chạy:
UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
CẬP NHẬT:
Dưới đây là một số điều bổ sung để thử có thể cải thiện hiệu suất của hoạt động này. Không ai được đảm bảo để giúp đỡ nhưng có lẽ đáng để thử nghiệm. Và với 100 triệu hàng để cập nhật, bạn có nhiều thời gian / cơ hội để kiểm tra một số biến thể ;-).
- Thêm vào
TOP (@UpdateRows)
truy vấn CẬP NHẬT để dòng trên cùng trông giống như:
UPDATE TOP (@UpdateRows) ht
Đôi khi, nó giúp trình tối ưu hóa biết có bao nhiêu hàng tối đa sẽ bị ảnh hưởng để không lãng phí thời gian tìm kiếm thêm.
Thêm một phím CHÍNH vào #CurrentSet
bảng tạm thời. Ý tưởng ở đây là giúp trình tối ưu hóa với bảng THAM GIA đến 100 triệu hàng.
Và chỉ cần tuyên bố để không mơ hồ, không nên có bất kỳ lý do nào để thêm PK vào #FullSet
bảng tạm thời vì đây chỉ là một bảng xếp hàng đơn giản trong đó thứ tự không liên quan.
- Trong một số trường hợp, nó giúp thêm Chỉ mục được Lọc để hỗ trợ các
SELECT
nguồn cấp dữ liệu đó vào #FullSet
bảng tạm thời. Dưới đây là một số cân nhắc liên quan đến việc thêm một chỉ mục như vậy:
- Điều kiện WHERE phải khớp với điều kiện WHERE của truy vấn của bạn, do đó
WHERE deleted is null or deletedDate is null
- Khi bắt đầu quá trình, hầu hết các hàng sẽ khớp với điều kiện WHERE của bạn, vì vậy một chỉ mục không hữu ích. Bạn có thể muốn đợi cho đến khi đâu đó khoảng 50% trước khi thêm điều này. Tất nhiên, nó giúp được bao nhiêu và khi nào tốt nhất để thêm chỉ số khác nhau do một số yếu tố, vì vậy đó là một chút thử nghiệm và lỗi.
- Bạn có thể phải cập nhật thủ công STATS và / hoặc REBUILD chỉ mục để cập nhật chỉ số vì dữ liệu cơ sở thay đổi khá thường xuyên
- Hãy nhớ rằng chỉ số, trong khi giúp
SELECT
, sẽ làm tổn thương UPDATE
vì nó là một đối tượng khác phải được cập nhật trong quá trình hoạt động đó, do đó có thêm I / O. Điều này đóng vai trò cả hai bằng cách sử dụng Chỉ mục được lọc (thu nhỏ khi bạn cập nhật các hàng vì có ít hàng khớp với bộ lọc hơn) và đợi một lát để thêm chỉ mục (nếu ban đầu nó sẽ không hữu ích lắm, thì không có lý do gì để phát sinh I / O bổ sung).