로그없이 SQL에서 테이블의 대용량 데이터를 삭제하는 방법은 무엇입니까?


128

큰 데이터 테이블이 있습니다. 이 테이블에는 1000 만 개의 레코드가 있습니다.

이 쿼리에 대한 가장 좋은 방법은 무엇입니까?

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

4
:) 모든 행을 readTime> = dateadd (MONTH, -7, GETDATE ()) 다른 테이블에 가져 오기 위해 일종의 ETL을 작성하지 않는 한 두렵습니다. , 로그에 쓰는 것을 막을 수 없습니다
TMNT2014

로깅은 탄력적 인 트랜잭션을 갖는 모든 기능 또는 기능이 없습니다. 일부 작업에 대한 로그가없는 것은 말 그대로 의미가 없습니다. 그렇지 않으면 로그가 쓸모가 없습니다.
Erik Philips

1
유지하려는 데이터를 내보내고 테이블을 자른 다음 다시 가져 오기
Bohemian

또 다른 옵션은 기록되지 않은 테이블 변수를 사용하는 것입니다. 따라서 readTime> = dateadd (MONTH, -7, GETDATE ()) 데이터를 테이블 변수에 저장 한 다음 원래 테이블을 자르고 테이블 변수에서 데이터를 다시 복사하십시오. 그러나 문제가 발생하여 테이블이 부주의하게 잘리는 경우를 대비하여 데이터를 백업 해 두겠습니다. :) 그리고 항상 적은 환경에서 스크립트를 테스트 실행하십시오.
TMNT2014 2014-06-13

답변:


203
  1. 해당 테이블의 모든 행을 삭제하는 경우 가장 간단한 옵션은 테이블을 자르는 것입니다.

    TRUNCATE TABLE LargeTable
    GO

    Truncate table은 단순히 테이블을 비우고 WHERE 절을 사용하여 삭제되는 행을 제한 할 수 없으며 트리거가 실행되지 않습니다.

  2. 반면에 데이터의 80-90 % 이상을 삭제하는 경우 총 1 천 1 백만 행이 있고 1 천만 행을 삭제하려는 경우 다른 방법은이 1 백만 행을 삽입하는 것입니다 (보관하려는 레코드 )을 다른 스테이징 테이블에 추가합니다. 이 대형 테이블을 자르고이 100 만 행을 다시 삽입하십시오.

  3. 또는이 테이블을 기본 테이블로 사용하는 권한 /보기 또는 기타 개체가이 테이블을 삭제해도 영향을받지 않는 경우 상대적으로 적은 양의 행을 다른 테이블로 가져 와서이 테이블을 삭제하고 동일한 스키마를 가진 다른 테이블을 만들고 가져올 수 있습니다. 이 ex-Large 테이블에 행을 다시 넣습니다.

  4. 내가 생각할 수있는 마지막 옵션은 데이터베이스를 변경 Recovery Mode to SIMPLE한 다음 이와 같은 while 루프를 사용하여 더 작은 일괄 처리로 행을 삭제하는 것입니다.

    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

그리고 복구 모드를 다시 전체로 변경하는 것을 잊지 마십시오. 완전히 적용되도록하려면 백업을 수행해야한다고 생각합니다 (변경 또는 복구 모드).


14
또한 테이블을 자르면 FK를 연결할 수 없습니다.
HLGEM

1
그러나 데이터의 80-90 %를 삭제하고 있는지 확인하는 방법은 무엇입니까? 삭제해야 할 값 범위 만 있다고 가정 해 보겠습니다. 그리고 테이블이 몇 개 있습니다. 그래서 저는 그것들을 모두 확인하고 백분율을 계산해야합니다. 그리고 약 30 % 정도라면이 방법은 그다지 효과적이지 않은 것 같습니다 ... 저는 알 수없는 경우에 대한 최적의 솔루션을 찾으려고 노력하고 있습니다.
Archont 2016

7
@Archont optimal solution for unknown case그게 꿈이지 않습니까? 안타깝게도 한 알약으로 모든 질병을 치료할 수는 없습니다. 여러 시나리오에 대해 몇 가지 가능한 솔루션을 제안했습니다. 불행히도 여기에는 은색 총알이 없습니다.
M.Ali 2016

5
옵션 4를 선택할 때주의해야 할 사항 : 테이블 사용 방법에 따라 잠금 에스컬레이션 을 방지하기 위해 한 번에 5000 개 미만의 행을 삭제하는 것이 더 나은 옵션 일 수 있습니다 .
Daniel

삭제할 레코드 수가 테이블에 남아있는 레코드보다 훨씬 더 큰 경우 원래 테이블에 남아있는 레코드의 임시 테이블을 선택하고 임시 테이블의 이름을 바꾸는 것이 훨씬 빠르다는 것을 알았습니다. 어딘가에서 ID ID 외래 키를 사용하지 않는다는 점을 감안할 때.
Vladimir Bozic

96

@ m-ali 대답은 맞지만 각 청크 후에 트랜잭션을 커밋하지 않고 체크 포인트를 수행하면 로그가 많이 커질 수 있음을 명심하십시오. 이것이 내가 그것을하는 방법 이며 성능 테스트 및 그래프와 함께 http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes 를 참조로 사용합니다.

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
사용 가능한 디스크 공간이 제한된 경우에 허용되는 대답이어야합니다. 없이 COMMIT TRANSACTION그리고 CHECKPOINT통나무는 여전히 성장하고 있습니다. 이것을 명확히 해주셔서 감사합니다.
gkoul

+1. @Deleted_Rows10000과 비교하고 싶 거나 작은 데이터 세트를 무기한 삭제하기 때문에 무한 루프가 발생할 수 있습니다. 따라서 WHILE (@Deleted_Rows = 10000)-삭제할 데이터의 전체 "페이지"가 ​​없으면 즉시 중지됩니다. 구현 WHILE (@Deleted_Rows > 0)에서 while 루프는 한 행만 삭제하더라도 다시 실행되며 다음 실행에서도 삭제할 행 또는 두 개를 찾을 수 있습니다. 결과적으로 무한 루프가 발생합니다.
NS du Toit

@NSduToit WHERE 절은 최소 7 개월 된 레코드를 고려하므로 삭제를 수행하는 동안 해당 조건을 충족하는 새 레코드가 없습니다.
Francisco Goldenstein

@FranciscoGoldenstein 글쎄요, 쿼리에 사용 된 날짜는 WHILE루프 자체 내에서 반복적으로 날짜를 계산하기 때문에 각 반복마다 다릅니다 dateadd(MONTH,-7,GETDATE())..
NS du Toit

@FranciscoGoldenstein 또한 아마도 이것 이외의 다른 사용 사례의 경우-아마도 새로운 데이터가 기본 테이블에 추가되어 WHILE루프의 다른 반복 사이에서 삭제 될 수있는 새 레코드가 생성 될 수 있습니다 .
NS du Toit

52

GO + 동일한 쿼리를 실행하려는 횟수를 사용할 수도 있습니다.

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

나는 이것을 좋아한다. 나는 실수로 같은 행을 테이블에 2,600 만 번 삽입하고 모든 항목을 삭제해야했습니다. 하나의 삭제 문에서 서버의 메모리가 부족했기 때문에 이것은 좋은 질문입니다 , 삭제할 행이 부족하면 중간 루프가 중지됩니까?
ScottC

2
@ScottC, 그것은 루프가 아니며 쿼리를 반복합니다 (일괄 처리). 행이 부족하면 아무것도 삭제할 수 없습니다. 그러나 그것은 멈추지 않을 것입니다. 삭제 한 행이 부족하면 (0 행이 영향을 받음)과 같은 것을 얻을 수 있습니다.
Bunkerbuster

아, 예, 질문을 게시 한 지 약 5 분 후에 삭제가 완료 되었기 때문에 매우 도움이되었습니다.
ScottC

1
이 구문은 어떤 MS SQL Server GO xx에서 작동합니까? 내가 얻을 "저장 프로 시저를 찾을 수 없습니다 '" 오류가 발생했습니다. GO명령 없이는 잘 작동합니다.
Abel

3
흠, 실행할 수있는 것 같고 실제로 여러 번 실행되지만 MS SQL Mgt Studio에서는 언급 된 오류와 함께 빨간색 곱슬 선이 표시됩니다 (하지만 F5 실행이 작동합니다)
Abel

11

@Francisco Goldenstein, 사소한 수정입니다. COMMIT는 변수를 설정 한 후에 사용해야합니다. 그렇지 않으면 WHILE이 한 번만 실행됩니다.

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

M.Ali 의이 변형은 저에게 잘 작동합니다. 일부를 삭제하고 로그를 지우고 반복합니다. 나는 로그가 커지고, 떨어지고, 다시 시작하는 것을보고 있습니다.

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

이것은 매우 유용했습니다! # of rows한 번에 삭제할 을 매개 변수화 하고 WHERE절도 수정했습니다 . 매력처럼 작동합니다!
Shiva

7

파티셔닝을 구현할 의향이 있고 가능한 경우 런타임 오버 헤드가 거의없이 대량의 데이터를 제거하는 효과적인 기술입니다. 그러나 일회성 운동에는 비용 효율적이지 않습니다.


4

2 천 1 백만 행의 테이블에서 몇 분 만에 1 천 9 백만 행을 삭제할 수있었습니다 . 여기 내 접근 방식이 있습니다.

당신이있는 경우 자동 증가 기본 키 이 테이블을, 당신은이 기본 키를 사용할 수있다.

  1. readTime <dateadd (MONTH, -7, GETDATE ()) 인 대형 테이블의 기본 키 최소값을 가져옵니다. (readTime에 인덱스를 추가합니다. 아직없는 경우이 인덱스는 3 단계의 테이블과 함께 삭제됩니다.) 변수 'min_primary'에 저장할 수 있습니다.

  2. 기본 키> min_primary가있는 모든 행을 준비 테이블 (행 수가 크지 않은 경우 메모리 테이블)에 삽입합니다.

  3. 큰 테이블을 삭제하십시오.

  4. 테이블을 다시 만듭니다. 준비 테이블의 모든 행을 기본 테이블로 복사합니다.

  5. 스테이징 테이블을 삭제하십시오.


3

다음과 같이 while 루프를 사용하여 작은 배치를 삭제할 수 있습니다.

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

또 다른 용도 :

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

선택 과목;

트랜잭션 로그가 활성화 된 경우 트랜잭션 로그를 비활성화합니다.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

더 짧은 구문

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

1

SQL Server 2016 이상을 사용하고 테이블에 삭제하려는 열 (예 : Timestamp 열)을 기반으로 생성 된 파티션이있는 경우이 새 명령을 사용하여 파티션별로 데이터를 삭제할 수 있습니다.

(파티션 ({|} [, ... n]))으로 테이블 자르기

이렇게하면 선택한 파티션의 데이터 만 삭제되며 트랜잭션 로그가 생성되지 않고 모든 데이터가 삭제되지 않고 일반 자르기만큼 빠르게 수행되므로 테이블의 일부에서 데이터를 삭제하는 가장 효율적인 방법입니다. 테이블에서.

단점은 테이블이 파티션으로 설정되지 않은 경우 구식으로 가서 정기적 인 접근 방식으로 데이터를 삭제 한 다음 나중에이를 수행 할 수 있도록 파티션이있는 테이블을 다시 만들어야한다는 것입니다. 삽입 절차 자체에 파티션 생성 및 삭제를 추가했습니다. 5 억 개의 행이있는 테이블이 있었기 때문에 이것이 삭제 시간을 줄이는 유일한 옵션이었습니다.

자세한 내용은 아래 링크를 참조하세요. https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL Server 2016 파티션이있는 테이블 자르기

다음은 필요한 데이터가있는 파티션이있는 테이블을 다시 생성하기 전에 먼저 데이터를 삭제하기 위해 수행 한 작업입니다. 이 쿼리는 데이터가 삭제 될 때까지 지정된 기간 동안 며칠 동안 실행됩니다.

: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

루프없이 말하면 GOTOSQL Server를 사용하여 많은 양의 레코드를 삭제 하는 문을 사용할 수 있습니다 . 엑사.

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

이런 식으로 작은 크기의 삭제로 많은 양의 데이터를 삭제할 수 있습니다.

더 많은 정보가 필요하면 알려주세요.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.