Xóa hàng triệu hàng khỏi bảng SQL


9

Tôi phải xóa hơn 16 triệu bản ghi khỏi bảng hàng hơn 221 triệu và nó đang diễn ra cực kỳ chậm.

Tôi đánh giá cao nếu bạn chia sẻ đề xuất để tạo mã dưới đây nhanh hơn:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

DECLARE @BATCHSIZE INT,
        @ITERATION INT,
        @TOTALROWS INT,
        @MSG VARCHAR(500);
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;

BEGIN TRY
    BEGIN TRANSACTION;

    WHILE @BATCHSIZE > 0
        BEGIN
            DELETE TOP (@BATCHSIZE) FROM MySourceTable
            OUTPUT DELETED.*
            INTO MyBackupTable
            WHERE NOT EXISTS (
                                 SELECT NULL AS Empty
                                 FROM   dbo.vendor AS v
                                 WHERE  VendorId = v.Id
                             );

            SET @BATCHSIZE = @@ROWCOUNT;
            SET @ITERATION = @ITERATION + 1;
            SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
            SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);             
            PRINT @MSG;
            COMMIT TRANSACTION;
            CHECKPOINT;
        END;
END TRY
BEGIN CATCH
    IF @@ERROR <> 0
       AND @@TRANCOUNT > 0
        BEGIN
            PRINT 'There is an error occured.  The database update failed.';
            ROLLBACK TRANSACTION;
        END;
END CATCH;
GO

Kế hoạch thực hiện (giới hạn trong 2 lần lặp)

nhập mô tả hình ảnh ở đây

VendorIdPKkhông được phân cụm , trong đó chỉ mục được phân cụm không được sử dụng bởi tập lệnh này. Có 5 chỉ mục không duy nhất, không phân cụm khác.

Nhiệm vụ là "loại bỏ các nhà cung cấp không tồn tại trong một bảng khác" và sao lưu chúng vào một bảng khác. Tôi có 3 bàn vendors, SpecialVendors, SpecialVendorBackups. Cố gắng loại bỏ SpecialVendorsnhững thứ không tồn tại trong Vendorsbảng và để sao lưu các bản ghi bị xóa trong trường hợp những gì tôi đang làm là sai và tôi phải đặt lại chúng trong một hoặc hai tuần.


Tôi sẽ làm việc để tối ưu hóa truy vấn đó và thử tham gia bên trái nơi null
paparazzo

Câu trả lời:


8

Kế hoạch thực hiện cho thấy rằng nó đang đọc các hàng từ một chỉ mục không bao gồm trong một số thứ tự sau đó thực hiện tìm kiếm cho mỗi hàng bên ngoài đọc để đánh giá NOT EXISTS

nhập mô tả hình ảnh ở đây

Bạn đang xóa 7,2% của bảng. 16.000.000 hàng trong 3.556 lô 4.500

Giả sử rằng các hàng đủ điều kiện được phân phối cuối cùng trong toàn bộ chỉ mục thì điều này có nghĩa là nó sẽ xóa khoảng 1 hàng mỗi 13,8 hàng.

Vì vậy, lần lặp 1 sẽ đọc 62,156 hàng và thực hiện rằng nhiều chỉ mục tìm kiếm trước khi tìm thấy 4.500 để xóa.

Lặp lại 2 sẽ đọc 57,656 (62,156 - 4,500) hàng chắc chắn sẽ không đủ điều kiện bỏ qua mọi cập nhật đồng thời (vì chúng đã được xử lý) và sau đó là 62,156 hàng khác để có 4.500 để xóa.

Lặp lại 3 sẽ đọc (2 * 57,656) + 62,156 hàng và cứ như vậy cho đến khi cuối cùng, lặp lại 3,556 sẽ đọc (3,555 * 57,656) + 62,156 hàng và thực hiện nhiều lần tìm kiếm.

Vì vậy, số lượng tìm kiếm chỉ mục được thực hiện trên tất cả các lô là SUM(1, 2, ..., 3554, 3555) * 57,656 + (3556 * 62156)

Đó là ((3555 * 3556 / 2) * 57656) + (3556 * 62156)- hoặc364,652,494,976

Tôi sẽ đề nghị bạn cụ thể hóa các hàng để xóa vào bảng tạm thời

INSERT INTO #MyTempTable
SELECT MySourceTable.PK,
       1 + ( ROW_NUMBER() OVER (ORDER BY MySourceTable.PK) / 4500 ) AS BatchNumber
FROM   MySourceTable
WHERE  NOT EXISTS (SELECT *
                   FROM   dbo.vendor AS v
                   WHERE  VendorId = v.Id) 

Và thay đổi DELETEđể xóa WHERE PK IN (SELECT PK FROM #MyTempTable WHERE BatchNumber = @BatchNumber)Bạn vẫn có thể cần phải bao gồm một NOT EXISTStrong các DELETEtruy vấn riêng của mình để phục vụ cho thông tin cập nhật từ bảng tạm thời được dân cư nhưng điều này nên hiệu quả hơn vì nó sẽ chỉ cần phải thực hiện 4.500 tìm kiếm mỗi đợt.


Khi bạn nói "cụ thể hóa các hàng cần xóa vào bảng tạm thời", bạn có đề xuất đặt tất cả các bản ghi đó với tất cả các cột của chúng vào bảng tạm thời không? hay chỉ PKcột? (Tôi tin rằng bạn đang đề nghị tôi chuyển hoàn toàn chúng sang bảng tạm thời nhưng muốn kiểm tra lại)
cilerler

@cilerler - Chỉ cần (các) cột chính
Martin Smith

bạn có thể nhanh chóng xem lại điều này nếu tôi hiểu đúng những gì bạn nói hay không?
cilerler

@cilerler - Có DELETE TOP (@BATCHSIZE) FROM MySourceTablenên DELETE FROM MySourceTable lập chỉ mục bảng tạm thời CREATE TABLE #MyTempTable ( Id BIGINT, BatchNumber BIGINT, PRIMARY KEY(BatchNumber, Id) );VendorIdchắc chắn là PK không? Bạn có> 221 triệu nhà cung cấp khác nhau?
Martin Smith

Cảm ơn Martin, sẽ kiểm tra nó sau 6 giờ tối. Và câu trả lời của bạn là, Đó chắc chắn là PK duy nhất tồn tại trong bảng đó
cilerler

4

Kế hoạch thực hiện cho thấy mỗi vòng lặp liên tiếp sẽ thực hiện nhiều công việc hơn vòng lặp trước. Giả sử rằng các hàng cần xóa được phân bổ đều trong bảng, vòng lặp đầu tiên sẽ cần quét khoảng 4500 * 221000000/16000000 = 62156 hàng để tìm 4500 hàng cần xóa. Nó cũng sẽ thực hiện cùng một số chỉ mục được tìm kiếm so với vendorbảng. Tuy nhiên, vòng lặp thứ hai sẽ cần đọc qua cùng 62156 - 4500 = 57656 hàng mà bạn đã không xóa lần đầu tiên. Chúng tôi có thể mong đợi vòng lặp thứ hai quét 120000 hàng từ MySourceTablevà thực hiện 120000 tìm kiếm so với vendorbảng. Số lượng công việc cần thiết cho mỗi vòng lặp tăng theo tốc độ tuyến tính. Như một xấp xỉ, chúng ta có thể nói rằng vòng lặp trung bình sẽ cần đọc 102516868 hàng từ MySourceTablevà để thực hiện 102516868 tìm kiếm chống lạivendorbàn. Để xóa 16 triệu hàng với kích thước lô 4500, mã của bạn cần thực hiện 16000000/4500 = 3556 vòng, do đó tổng số lượng công việc để mã của bạn hoàn thành là khoảng 364,5 tỷ hàng được đọc từ MySourceTablevà 364,5 tỷ chỉ số tìm kiếm.

Một vấn đề nhỏ hơn là bạn sử dụng biến cục bộ @BATCHSIZEtrong biểu thức TOP mà không có RECOMPILEhoặc một số gợi ý khác. Trình tối ưu hóa truy vấn sẽ không biết giá trị của biến cục bộ đó khi tạo một kế hoạch. Nó sẽ cho rằng nó bằng 100. Trong thực tế, bạn đang xóa 4500 hàng thay vì 100 và bạn có thể kết thúc với một kế hoạch kém hiệu quả hơn do sự khác biệt đó. Ước tính cardinality thấp khi chèn vào bảng cũng có thể gây ra hiệu quả. SQL Server có thể chọn một API nội bộ khác để thực hiện chèn nếu nó nghĩ rằng nó cần chèn 100 hàng thay vì 4500 hàng.

Một cách khác là chỉ cần chèn các khóa chính / khóa cụm của các hàng mà bạn muốn xóa vào một bảng tạm thời. Tùy thuộc vào kích thước của các cột chính của bạn, điều này có thể dễ dàng phù hợp với tempdb. Bạn có thể nhận được đăng nhập tối thiểu trong trường hợp đó có nghĩa là nhật ký giao dịch sẽ không nổ tung. Bạn cũng có thể nhận được ghi nhật ký tối thiểu đối với bất kỳ cơ sở dữ liệu nào với mô hình khôi phục SIMPLE. Xem liên kết để biết thêm thông tin về các yêu cầu.

Nếu đó không phải là một tùy chọn thì bạn nên thay đổi mã của mình để có thể tận dụng chỉ mục được nhóm trên MySourceTable. Điều quan trọng là viết mã của bạn để bạn thực hiện xấp xỉ cùng một lượng công việc trên mỗi vòng lặp. Bạn có thể làm điều đó bằng cách tận dụng chỉ mục thay vì chỉ quét bảng từ đầu mỗi lần. Tôi đã viết một bài đăng trên blog về một số phương pháp lặp khác nhau. Các ví dụ trong bài đăng đó chèn vào bảng thay vì xóa nhưng bạn sẽ có thể điều chỉnh mã.

Trong mã ví dụ dưới đây, tôi giả sử rằng khóa chính và khóa cụm của bạn MySourceTable. Tôi đã viết mã này khá nhanh và không thể kiểm tra nó:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

DECLARE @BATCHSIZE INT,
        @ITERATION INT,
        @TOTALROWS INT,
        @MSG VARCHAR(500)
        @STARTID BIGINT,
        @NEXTID BIGINT;
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;

SELECT @STARTID = ID
FROM MySourceTable
ORDER BY ID
OFFSET 0 ROWS
FETCH FIRST 1 ROW ONLY;

SELECT @NEXTID = ID
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
OFFSET (60000) ROWS
FETCH FIRST 1 ROW ONLY;

BEGIN TRY
    BEGIN TRANSACTION;

    WHILE @STARTID IS NOT NULL
        BEGIN
            WITH MySourceTable_DELCTE AS (
                SELECT TOP (60000) *
                FROM MySourceTable
                WHERE ID >= @STARTID
                ORDER BY ID
            )           
            DELETE FROM MySourceTable_DELCTE
            OUTPUT DELETED.*
            INTO MyBackupTable
            WHERE NOT EXISTS (
                                 SELECT NULL AS Empty
                                 FROM   dbo.vendor AS v
                                 WHERE  VendorId = v.Id
                             );

            SET @BATCHSIZE = @@ROWCOUNT;
            SET @ITERATION = @ITERATION + 1;
            SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
            SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);             
            PRINT @MSG;
            COMMIT TRANSACTION;

            CHECKPOINT;

            SET @STARTID = @NEXTID;
            SET @NEXTID = NULL;

            SELECT @NEXTID = ID
            FROM MySourceTable
            WHERE ID >= @STARTID
            ORDER BY ID
            OFFSET (60000) ROWS
            FETCH FIRST 1 ROW ONLY;

        END;
END TRY
BEGIN CATCH
    IF @@ERROR <> 0
       AND @@TRANCOUNT > 0
        BEGIN
            PRINT 'There is an error occured.  The database update failed.';
            ROLLBACK TRANSACTION;
        END;
END CATCH;
GO

Phần quan trọng là đây:

WITH MySourceTable_DELCTE AS (
    SELECT TOP (60000) *
    FROM MySourceTable
    WHERE ID >= @STARTID
    ORDER BY ID
)   

Mỗi vòng lặp sẽ chỉ đọc 60000 hàng từ MySourceTable. Điều đó sẽ dẫn đến kích thước xóa trung bình là 4500 hàng cho mỗi giao dịch và kích thước xóa tối đa là 60000 hàng cho mỗi giao dịch. Nếu bạn muốn thận trọng hơn với cỡ lô nhỏ hơn thì cũng tốt. Các @STARTIDtiến bộ biến sau mỗi vòng lặp, do đó bạn có thể tránh được đọc cùng hàng nhiều hơn một lần từ bảng nguồn.


Cảm ơn bạn đã thông tin chi tiết. Tôi đặt giới hạn 4500 để không khóa bảng. Nếu tôi không nhầm thì SQL có giới hạn cứng sẽ khóa toàn bộ bảng nếu số lần xóa vượt quá 5000. Và vì đây sẽ là một quá trình dài, tôi không thể nỗ lực để khóa bảng đó trong một khoảng thời gian dài. Nếu tôi đặt 60000 đến 4500, bạn có nghĩ rằng tôi sẽ có được hiệu suất tương tự không?
cilerler

@cilerler Nếu bạn lo lắng về việc leo thang khóa, bạn có thể vô hiệu hóa nó ở cấp độ bảng. Không có gì sai khi sử dụng kích thước lô 4500. Điều quan trọng là mỗi vòng lặp sẽ thực hiện cùng một lượng công việc.
Joe Obbish

Tôi phải chấp nhận câu trả lời khác do sự khác biệt về tốc độ. Tôi đã thử nghiệm giải pháp của bạn và giải pháp của @ Martin-Smith và phiên bản của anh ấy sẽ nhận được nhiều dữ liệu hơn ~ 2% trong 10 phút thử nghiệm. Các giải pháp của bạn tốt hơn nhiều so với của tôi và tôi thực sự đánh giá cao thời gian của bạn ... -
cilerler

2

Hai ý nghĩ nảy ra trong đầu:

Sự chậm trễ có lẽ là do lập chỉ mục với khối lượng dữ liệu đó. Hãy thử bỏ các chỉ mục, xóa và xây dựng lại các chỉ mục.

Hoặc là..

Có thể nhanh hơn để sao chép các hàng bạn muốn giữ vào một bảng tạm thời, thả bảng có 16 triệu hàng và đổi tên bảng tạm thời (hoặc sao chép sang phiên bản mới của bảng nguồ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.