máy chủ sql: cập nhật các trường trên bảng lớn trong khối nhỏ: làm thế nào để có được tiến trình / trạng thái?


10

Chúng tôi có một bảng rất lớn (hàng 100 triệu) và chúng tôi cần cập nhật một vài trường trên đó.

Đối với vận chuyển gỗ, vv, rõ ràng, chúng tôi cũng muốn giữ nó cho các giao dịch có quy mô lớn.

  • Dưới đây sẽ làm những mẹo?
  • Và làm thế nào chúng ta có thể làm cho nó in một số đầu ra, để chúng ta có thể thấy sự tiến bộ? (chúng tôi đã thử thêm một câu lệnh IN trong đó, nhưng không có gì là đầu ra trong vòng lặp while)

Mã này là:

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

Câu trả lời:


12

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 -3trong 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ể ;-).

  1. 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.
  2. Thêm một phím CHÍNH vào #CurrentSetbả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 #FullSetbả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.

  3. Trong một số trường hợp, nó giúp thêm Chỉ mục được Lọc để hỗ trợ các SELECTnguồn cấp dữ liệu đó vào #FullSetbả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:
    1. Đ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
    2. 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.
    3. 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
    4. Hãy nhớ rằng chỉ số, trong khi giúp SELECT, sẽ làm tổn thương UPDATEvì 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).

1
Thật tuyệt vời. Tôi đang chạy nó bây giờ, và nó hút thuốc rằng chúng ta có thể chạy nó trực tuyến vào ban ngày. Cảm ơn bạn!
Jonesome phục hồi Monica

@samsmith Vui lòng xem phần CẬP NHẬT mà tôi vừa thêm vì có một số ý tưởng có khả năng làm cho quá trình thực hiện nhanh hơn.
Solomon Rutzky

Nếu không có các cải tiến CẬP NHẬT, chúng tôi sẽ nhận được khoảng 8 triệu cập nhật / giờ ... với @BatchRows được đặt thành 10000000 (mười triệu)
Jonesome Rebstate Monica

@samsmith Thật tuyệt :) đúng không? Hãy ghi nhớ hai điều: 1) Quá trình sẽ chậm lại vì ngày càng có ít hàng khớp với mệnh đề WHERE, do đó sẽ là thời điểm tốt để thêm chỉ mục được lọc, nhưng bạn đã thêm chỉ mục không được lọc vào bắt đầu vì vậy tôi không chắc điều đó sẽ giúp ích hay làm tổn thương, nhưng tôi vẫn hy vọng thông lượng sẽ giảm khi gần đến mức hoàn thành và 2) bạn có thể tăng thông lượng bằng cách giảm WAITFOR DELAYxuống còn nửa giây hoặc lâu hơn, nhưng đó là một sự đánh đổi với sự tương tranh và có thể là bao nhiêu được gửi qua vận chuyển log.
Solomon Rutzky

Chúng tôi hài lòng với 8 triệu hàng / giờ. Vâng, chúng ta có thể thấy nó chậm lại. Chúng tôi do dự để tạo thêm bất kỳ chỉ mục nào (vì bảng bị khóa cho toàn bộ bản dựng). Những gì chúng tôi đã làm một vài lần là thực hiện một reorg trên chỉ mục hiện có (bởi vì đó là trên dòng).
Jonesome phục hồi Monica

4

Trả lời phần thứ hai: làm thế nào để in một số đầu ra trong vòng lặp.

Tôi có một vài quy trình bảo trì dài mà đôi khi quản trị viên hệ thống phải chạy.

Tôi chạy chúng từ SSMS và cũng nhận thấy rằng PRINTcâu lệnh chỉ được hiển thị trong SSMS sau khi toàn bộ thủ tục kết thúc.

Vì vậy, tôi đang sử dụng RAISERRORvới mức độ nghiêm trọng thấp:

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

Tôi đang sử dụng SQL Server 2008 Standard và SSMS 2012 (11.0.3128.0). Dưới đây là một ví dụ hoạt động hoàn chỉnh để chạy trong 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

Khi tôi nhận xét RAISERRORvà chỉ để lại PRINTcác tin nhắn trong tab Tin nhắn trong SSMS chỉ xuất hiện sau khi toàn bộ lô kết thúc, sau 6 giây.

Khi tôi nhận xét PRINTvà sử dụng RAISERRORcác thông báo trong tab Tin nhắn trong SSMS xuất hiện mà không cần chờ 6 giây, nhưng khi vòng lặp tiến triển.

Thật thú vị, khi tôi sử dụng cả hai RAISERRORPRINT, tôi thấy cả hai tin nhắn. Đầu tiên là tin nhắn từ đầu tiên RAISERROR, sau đó trì hoãn trong 2 giây, sau đó đầu tiên PRINTvà thứ hai RAISERROR, v.v.


Trong các trường hợp khác, tôi sử dụng một logbảng chuyên dụng riêng biệt và chỉ cần chèn một hàng vào bảng với một số thông tin mô tả trạng thái hiện tại và dấu thời gian của quy trình chạy dài.

Trong khi quá trình dài chạy tôi định kỳ SELECTtừ logbảng để xem những gì đang xảy ra.

Điều này rõ ràng có chi phí nhất định, nhưng nó để lại một bản ghi (hoặc lịch sử của các bản ghi) mà tôi có thể kiểm tra theo tốc độ của riêng mình sau này.


Trên SQL 2008/2014, chúng tôi không thể thấy kết quả từ nâng cao .... chúng tôi còn thiếu gì?
Jonesome phục hồi

@samsmith, tôi đã thêm một ví dụ hoàn chỉnh. Thử nó. Những hành vi nào bạn nhận được trong ví dụ đơn giản này?
Vladimir Baranov

2

Bạn có thể theo dõi nó từ một kết nối khác với một cái gì đó như:

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

để xem còn lại bao nhiêu để làm. Điều này có thể hữu ích nếu một ứng dụng đang gọi quy trình, thay vì bạn chạy thủ công trong SSMS hoặc tương tự, và cần hiển thị tiến trình: chạy quy trình chính không đồng bộ (hoặc trên một luồng khác) và sau đó lặp lại gọi "còn lại bao nhiêu "Kiểm tra mọi lúc cho đến khi cuộc gọi async (hoặc chuỗi) hoàn thành.

Đặt mức cô lập càng lỏng lẻo càng tốt có nghĩa là điều này sẽ trở lại trong thời gian hợp lý mà không bị kẹt sau giao dịch chính do các vấn đề khóa. Điều đó có thể có nghĩa là giá trị trả về là một chút không chính xác tất nhiên, nhưng như một máy đo tiến độ đơn giản, điều này không thành vấn đề.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.