Làm thế nào để xóa dữ liệu lớn của bảng trong SQL mà không cần nhật ký?


127

Tôi có một bảng dữ liệu lớn. Có 10 triệu hồ sơ trong bảng này.

Cách tốt nhất cho truy vấn này là gì

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

4
:) Tôi e ngại trừ khi bạn sẵn sàng viết một số loại ETL để nhận tất cả các hàng readTime> = dateadd (MONTH, -7, GETDATE ()) vào một bảng khác và sau đó phát hành bảng Truncate và đưa dữ liệu trở lại bằng ETL , bạn sẽ không thể ngăn nó ghi vào nhật ký
TMNT2014

Ghi nhật ký là một chức năng tất cả hoặc không có gì để có một giao dịch linh hoạt. Nghĩa đen là không có nhật ký cho một số hoạt động nhưng không có nhật ký, nếu không thì nhật ký là vô ích.
Erik Philips

1
Xuất dữ liệu bạn muốn giữ, cắt bớt bảng, sau đó nhập lại vào
Bohemian

Một tùy chọn khác sẽ được sử dụng một bảng có thể không được ghi lại. Do đó lưu trữ dữ liệu readTime> = dateadd (MONTH, -7, GETDATE ()) của bạn trong một biến bảng và sau đó cắt bớt bảng gốc và sao chép lại dữ liệu từ biến bảng. Tuy nhiên, tôi sẽ sao lưu dữ liệu trong trường hợp có sự cố và bảng vô tình bị cắt ngắn. :) Và luôn luôn chạy thử tập lệnh của bạn trên một môi trường nhỏ hơn.
TMNT2014

Câu trả lời:


203
  1. Nếu bạn đang xóa Tất cả các hàng trong bảng đó, tùy chọn đơn giản nhất là cắt ngắn bảng, đại loại như

    TRUNCATE TABLE LargeTable
    GO

    Bảng rút gọn sẽ đơn giản làm trống bảng, bạn không thể sử dụng mệnh đề WHERE để giới hạn các hàng bị xóa và sẽ không có kích hoạt nào được kích hoạt.

  2. Mặt khác, nếu bạn đang xóa hơn 80-90 Phần trăm dữ liệu, hãy nói rằng nếu bạn có tổng số 11 triệu hàng và bạn muốn xóa 10 triệu cách khác sẽ là Chèn 1 triệu hàng này (các bản ghi bạn muốn giữ ) đến một bàn dàn khác. Cắt bớt bảng lớn này và chèn lại 1 triệu hàng này.

  3. Hoặc nếu các quyền / khung nhìn hoặc các đối tượng khác có bảng lớn này vì bảng bên dưới của chúng không bị ảnh hưởng bằng cách bỏ bảng này, bạn có thể lấy một lượng tương đối nhỏ các hàng này vào một bảng khác thả bảng này và tạo một bảng khác có cùng lược đồ và nhập các bảng này hàng trở lại vào bảng cũ này.

  4. Một tùy chọn cuối cùng tôi có thể nghĩ đến là thay đổi cơ sở dữ liệu của bạn Recovery Mode to SIMPLEvà sau đó xóa các hàng trong các lô nhỏ hơn bằng cách sử dụng vòng lặp while như thế này ..

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

và đừng quên thay đổi chế độ Recovery trở lại đầy đủ và tôi nghĩ rằng bạn phải sao lưu để làm cho nó hoàn toàn ảnh hưởng (chế độ thay đổi hoặc khôi phục).


14
Cũng nên nhớ rằng nếu bạn cắt bớt một bảng, bạn không thể có bất kỳ FK nào bị ám sát với nó.
HLGEM

1
Nhưng làm thế nào để chắc chắn rằng bạn đang xóa 80-90% dữ liệu? Giả sử tôi chỉ có một loạt các giá trị nên được xóa. Và tôi có một vài bảng. Vì vậy, tôi phải kiểm tra từng người trong số họ và tính toán tỷ lệ phần trăm, và nếu khoảng 30% tôi đoán phương pháp này không hiệu quả lắm ... Tôi đang cố gắng tìm giải pháp tối ưu cho trường hợp không xác định.
Archont

7
@Archont optimal solution for unknown caseđó là giấc mơ phải không? Thật không may, bạn không thể chữa mọi bệnh bằng bất kỳ một viên thuốc nào; Tôi đã đề xuất một số giải pháp có thể cho các kịch bản khác nhau. Không có viên đạn cúi ở đây không may.
M.Ali

5
Một điều cần lưu ý nếu chọn tùy chọn 4: Tùy thuộc vào cách sử dụng bảng, có thể là một tùy chọn tốt hơn để xóa ít hơn 5000 hàng cùng một lúc để tránh leo thang khóa .
Daniel

Nếu số lượng bản ghi cần xóa lớn hơn nhiều thì các bản ghi sẽ còn trong bảng, tôi thấy rằng việc chọn đơn giản vào bảng tạm thời của các bản ghi sẽ lưu lại và thả bảng gốc và đổi tên bảng tạm thời nhanh hơn nhiều. Cho rằng bạn không sử dụng mã nhận dạng Id khóa ngoại ở đâu đó.
Vladimir Bozic

95

Câu trả lời @ m-ali là đúng nhưng cũng nên nhớ rằng nhật ký có thể phát triển rất nhiều nếu bạn không thực hiện giao dịch sau mỗi đoạn và thực hiện một điểm kiểm tra. Đây là cách tôi sẽ làm và lấy bài viết này http://sqlperformance.com/2013/03/io-subystem/chunk-deletes làm tài liệu tham khảo, với các bài kiểm tra hiệu suất và đồ thị:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
Đây phải là câu trả lời được chấp nhận trong trường hợp không gian đĩa trống bị hạn chế. Không có COMMIT TRANSACTIONCHECKPOINTcác bản ghi vẫn đang phát triển. Cảm ơn đã làm rõ điều này.
gkoul

+1. Chỉ cần lưu ý rằng bạn có thể muốn so sánh @Deleted_Rowsvới 10000 hoặc bạn có thể kết thúc bằng một vòng lặp vô hạn do nó xóa vô thời hạn các bộ dữ liệu nhỏ. Vì vậy WHILE (@Deleted_Rows = 10000)- ngay khi không có một "trang" dữ liệu đầy đủ để xóa, nó sẽ dừng lại. Trong triển khai của bạn, WHILE (@Deleted_Rows > 0)vòng lặp while sẽ thực hiện lại ngay cả khi nó chỉ xóa một hàng và lần thực hiện tiếp theo cũng có thể tìm thấy một hoặc hai hàng để xóa - dẫn đến một vòng lặp vô hạn.
NS du Toit

@NSduĐể mệnh đề WHERE đang xem xét các hồ sơ ít nhất 7 tháng tuổi nên sẽ không có hồ sơ mới đáp ứng điều kiện đó trong khi bạn đang thực hiện xóa.
Francisco Goldenstein

@FranciscoGoldenstein Chà, ngày được sử dụng trong truy vấn sẽ khác nhau với mỗi lần lặp khi bạn liên tục tính ngày trong vòng WHILE vòng lặp : dateadd(MONTH,-7,GETDATE()).
NS du Toit

@FranciscoGoldenstein Ngoài ra, có thể đối với các trường hợp sử dụng khác ngoài trường hợp này - có thể dữ liệu mới được thêm vào bảng bên dưới sẽ dẫn đến các bản ghi mới có thể bị xóa giữa các lần lặp khác nhau của WHILEvòng lặp.
NS du Toit

52

Bạn cũng có thể sử dụng GO + bao nhiêu lần bạn muốn thực hiện cùng một truy vấn.

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

Tôi thích điều này, nó hoạt động với tôi Tôi vô tình chèn cùng một hàng vào một bảng 26 triệu lần và cần phải xóa tất cả các lần xuất hiện của nó, trong một câu lệnh xóa duy nhất đã hết bộ nhớ trên máy chủ, vì vậy đây là một câu hỏi tuyệt vời , nó sẽ dừng mid loop nếu hết hàng để xóa?
ScottC

2
@ScottC, nó không phải là một vòng lặp, nó chỉ lặp lại truy vấn (đợt như) và nếu bạn hết hàng thì nó không thể xóa bất cứ thứ gì. Nhưng nó sẽ không dừng lại. bạn sẽ nhận được một cái gì đó như (0 hàng) bị ảnh hưởng) nếu nó hết hàng bạn xóa.
Bunkerbuster

ah, vâng tôi phát hiện ra rằng khoảng 5 phút sau khi tôi đăng câu hỏi của mình, kể từ khi xóa xong, cảm ơn điều này rất hữu ích!
ScottC

1
Từ cú pháp MS SQL Server này GO xxlà gì để làm việc? Tôi gặp lỗi "Không thể tìm thấy thủ tục được lưu trữ ''" . Không có GOlệnh, nó hoạt động tốt mặc dù.
Abel

3
Hmm, có vẻ như tôi có thể thực thi nó và nó thực sự chạy nhiều lần, nhưng trong MS SQL Mgt Studio, nó hiển thị đường cong màu đỏ với lỗi được đề cập (nhưng sau đó F5 hoạt động)
Abel

11

@Francisco Goldenstein, chỉ là một sự điều chỉnh nhỏ. CAM KẾT phải được sử dụng sau khi bạn đặt biến, nếu không, WHILE sẽ được thực thi chỉ một lần:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

Biến thể này của M.Ali đang hoạt động tốt đối với tôi. Nó xóa một số, xóa nhật ký và lặp lại. Tôi đang xem nhật ký phát triển, thả và bắt đầu lại.

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

Điều này rất hữu ích! Tôi đã sửa đổi nó để tham số hóa # of rowsđể xóa tại một thời điểm, và cả WHEREmệnh đề. Hoạt động như một lá bùa!
Shiva

7

Nếu bạn sẵn sàng (và có thể) thực hiện phân vùng, đó là một kỹ thuật hiệu quả để loại bỏ một lượng lớn dữ liệu với ít chi phí thời gian chạy. Không hiệu quả chi phí cho một bài tập một lần, mặc dù.


4

Tôi đã có thể xóa 19 triệu hàng khỏi bảng 21 triệu hàng trong vài phút . Đây là cách tiếp cận của tôi.

Nếu bạn có khóa chính tăng tự động trên bảng này, thì bạn có thể sử dụng khóa chính này.

  1. Nhận giá trị tối thiểu của khóa chính của bảng lớn trong đó readTime <dateadd (MONTH, -7, GETDATE ()). (Thêm chỉ mục vào readTime, nếu chưa có, chỉ mục này sẽ bị xóa cùng với bảng ở bước 3.). Hãy lưu trữ nó trong một biến 'min_primary'

  2. Chèn tất cả các hàng có khóa chính> min_primary vào bảng phân tầng (bảng bộ nhớ nếu số hàng không lớn).

  3. Thả bàn lớn.

  4. Tái tạo bảng. Sao chép tất cả các hàng từ bảng phân tầng sang bảng chính.

  5. Thả bàn dàn.


3

Bạn có thể xóa các lô nhỏ bằng cách sử dụng vòng lặp while, đại loại như thế này:

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

Công dụng khác:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

Không bắt buộc;

Nếu nhật ký giao dịch được bật, hãy tắt nhật ký giao dịch.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Cú pháp ngắn hơn

select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
  DELETE TOP (10000) LargeTable 
  WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

1

Nếu bạn đang sử dụng máy chủ SQL 2016 trở lên và nếu bảng của bạn có các phân vùng được tạo dựa trên cột bạn đang cố xóa (ví dụ cột Dấu thời gian), thì bạn có thể sử dụng lệnh mới này để xóa dữ liệu theo phân vùng.

BẢNG TRUNCATE VỚI (PHẦN THAM GIA ({|} [, ... n]))

Điều này sẽ chỉ xóa dữ liệu trong (các) phân vùng đã chọn và là cách hiệu quả nhất để xóa dữ liệu khỏi một phần của bảng vì nó sẽ không tạo nhật ký giao dịch và sẽ được thực hiện nhanh như cắt ngắn thông thường nhưng không xóa tất cả dữ liệu từ cái bàn.

Hạn chế là nếu bảng của bạn không được thiết lập với phân vùng, thì bạn cần phải đi học cũ và xóa dữ liệu theo cách tiếp cận thông thường và sau đó tạo lại bảng với các phân vùng để bạn có thể làm điều này trong tương lai, đó là những gì tôi đã làm. Tôi đã thêm việc tạo và xóa phân vùng vào thủ tục chèn chính nó. Tôi đã có bảng với 500 triệu hàng nên đây là lựa chọn duy nhất để giảm thời gian xóa.

Để biết thêm chi tiết, hãy tham khảo các liên kết dưới đây: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

Máy chủ SQL 2016 Bảng rút gọn với các phân vùng

Dưới đây là những gì tôi đã làm trước tiên để xóa dữ liệu trước khi tôi có thể tạo lại bảng với các phân vùng có dữ liệu cần thiết trong đó. Truy vấn này sẽ chạy trong nhiều ngày trong cửa sổ thời gian được chỉ định cho đến khi dữ liệu bị xóa.

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

Nếu tôi nói không có vòng lặp, tôi có thể sử dụng GOTOcâu lệnh để xóa số lượng lớn các bản ghi bằng máy chủ sql. exa

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

như cách này, bạn có thể xóa một lượng lớn dữ liệu với kích thước xóa nhỏ hơn.

cho tôi biết nếu cần thêm thông tin

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.