Como excluir grandes dados da tabela no SQL sem log?


127

Eu tenho uma tabela de dados grande. Existem 10 milhões de registros nesta tabela.

Qual é a melhor maneira para esta consulta

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

4
:) Receio, a menos que você esteja disposto a escrever algum tipo de ETL para obter todas as linhas readTime> = dateadd (MONTH, -7, GETDATE ()) em outra tabela e, em seguida, emita uma tabela Truncate e coloque os dados novamente usando ETL , você não seria capaz de impedir que ele fosse
gravado

O log é uma função de tudo ou nada de ter transações resilientes. Literalmente, não faz sentido não ter um log para algumas operações, mas outras não; caso contrário, o log é inútil.
Erik Philips

1
Exporte os dados que deseja manter, trunque a tabela e depois importe-os novamente.
Bohemian

Outra opção seria usar uma variável de tabela que não é registrada. Portanto, armazene seus dados readTime> = dateadd (MONTH, -7, GETDATE ()) em uma variável da tabela e, em seguida, trunque a tabela original e copie os dados da variável da tabela. No entanto, eu manteria um backup dos dados caso algo desse errado e a tabela fosse inadvertidamente truncada. :) E sempre faça uma execução de teste do seu script em um ambiente menor.
TMNT2014

Respostas:


203
  1. Se você estiver excluindo todas as linhas dessa tabela, a opção mais simples é truncar tabela, algo como

    TRUNCATE TABLE LargeTable
    GO

    A tabela truncada simplesmente esvaziará a tabela, você não pode usar a cláusula WHERE para limitar as linhas que estão sendo excluídas e nenhum acionador será acionado.

  2. Por outro lado, se você estiver excluindo mais de 80 a 90% dos dados, digamos que você tenha um total de 11 milhões de linhas e deseje excluir 10 milhões de outra maneira seria Inserir esses 1 milhão de linhas (registros que você deseja manter ) para outra tabela de preparação. Trunque esta tabela grande e insira novamente essas 1 milhão de linhas.

  3. Ou, se permissões / visualizações ou outros objetos que possuem essa tabela grande como sua tabela subjacente não forem afetados ao soltar essa tabela, você poderá obter essa quantidade relativamente pequena de linhas em outra tabela, solte esta tabela e crie outra tabela com o mesmo esquema e importe-as linhas de volta para essa tabela ex-Large.

  4. Uma última opção em que consigo pensar é alterar as configurações de seu banco de dados. Recovery Mode to SIMPLE e excluir linhas em lotes menores usando um loop while, algo como isto.

    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

e não se esqueça de alterar o modo de recuperação para completo e acho que você precisa fazer um backup para torná-lo totalmente afetivo (os modos de alteração ou recuperação).


14
Lembre-se também de que, se você truncar uma tabela, não poderá ter nenhum FK associado.
HLGEM

1
Mas como ter certeza de que você está excluindo 80-90% dos dados? Vamos supor que eu tenha apenas um intervalo de valores que devem ser excluídos. E eu tenho algumas mesas. Então, eu tenho que verificar todos eles e calcular a porcentagem, e se cerca de 30% acho que esse método não é muito eficaz ... Estou tentando encontrar a solução ideal para casos desconhecidos.
Archont

7
@ Archont optimal solution for unknown caseesse é o sonho, não é? Infelizmente você não pode curar todas as doenças com qualquer comprimido; Sugeri algumas soluções possíveis para diferentes cenários. Infelizmente não há bala de lasca aqui.
M.Ali

5
Uma coisa a ser observada ao escolher a opção 4: Dependendo de como a tabela é usada, pode ser uma opção melhor excluir menos de 5000 linhas por vez para evitar o escalonamento de bloqueios .
Daniel

Se a contagem de registros a serem excluídos for muito maior do que os registros que permanecerão na tabela, descobri que a simples seleção na tabela temporária dos registros que permanecerão na tabela original e a eliminação da tabela original e a renomeação da tabela temporária serão muito mais rápidas. Como você não usa a chave estrangeira do ID de identidade em algum lugar.
Vladimir Bozic

95

A resposta @ m-ali está correta, mas lembre-se de que os logs podem crescer muito se você não confirmar a transação após cada bloco e executar um ponto de verificação. É assim que eu faria e considero este artigo http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes como referência, com testes de desempenho e gráficos:

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
Essa deve ser a resposta aceita caso o espaço em disco disponível seja limitado. Sem COMMIT TRANSACTIONe CHECKPOINTos logs ainda estão crescendo. Obrigado por deixar isso claro.
gkoul 13/03

+1. Observe que você pode comparar @Deleted_Rowscom 10000 ou pode acabar com um loop infinito devido à exclusão indefinida de pequenos conjuntos de dados. Portanto WHILE (@Deleted_Rows = 10000), assim que não houver uma "página" completa de dados para excluí-los, será interrompido. Na sua implementação, WHILE (@Deleted_Rows > 0)o loop while será executado novamente, mesmo que tenha excluído apenas uma linha, e a próxima execução também poderá encontrar uma ou duas linhas para excluir - resultando em um loop infinito.
NS du Toit

A @NSduToit da cláusula WHERE está considerando registros com pelo menos 7 meses de idade, para que não haja novos registros que atendam a essa condição enquanto você estiver executando a exclusão.
Francisco Goldenstein

@FranciscoGoldenstein Bem, a data usada na consulta será diferente com cada iteração como você calcular repetidamente a data dentro do WHILEpróprio loop: dateadd(MONTH,-7,GETDATE()).
NS du Toit

@FranciscoGoldenstein Além disso, talvez para outros casos de uso que não este - talvez novos dados sejam adicionados à tabela subjacente que resultarão em novos registros que podem ser excluídos entre diferentes iterações do WHILEloop.
NS du Toit

52

Você também pode usar o GO + quantas vezes deseja executar a mesma consulta.

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

Eu gosto disso, está funcionando para mim. Eu acidentalmente inseri a mesma linha em uma tabela 26 milhões de vezes e precisava excluir todas as ocorrências, o que em uma única instrução de exclusão ficou sem memória no servidor, então essa é uma ótima pergunta. , ele interromperá o loop intermediário se ficar sem linhas para excluir?
ScottC

2
@ ScottC, não é um loop, apenas repete a consulta (tipo lote) e, se você ficar sem linhas, não poderá excluir nada. Mas não vai parar. você terá algo como (0 linha (s) afetada) se ficar sem as linhas que você excluir.
Bunkerbuster

ah, sim, descobri que cerca de 5 minutos depois que postei minha pergunta, desde que minha exclusão terminou, obrigado, isso foi muito útil!
ScottC

1
De que MS SQL Server essa sintaxe GO xxdeve funcionar? Eu recebo o erro "Não foi possível encontrar o procedimento armazenado ''" . Sem o GOcomando, ele funciona bem.
Abel

3
Hmm, parece que eu posso executá-lo, e ele é executado várias vezes, mas no MS SQL Mgt Studio ele mostra a linha encaracolada vermelha com o erro mencionado (mas a execução do F5 funciona então)
Abel

11

@Francisco Goldenstein, apenas uma pequena correção. O COMMIT deve ser usado após você definir a variável, caso contrário, o WHILE será executado apenas uma vez:

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

Essa variação de M.Ali está funcionando bem para mim. Exclui alguns, limpa o log e repete. Estou vendo o log crescer, cair e começar de novo.

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

Isso foi muito útil! Eu o modifiquei para parametrizar o # of rowspara excluir de cada vez, e também a WHEREcláusula. Funciona como um encanto!
Shiva

7

Se você deseja (e é capaz) implementar o particionamento, essa é uma técnica eficaz para remover grandes quantidades de dados com pouco tempo de execução. Não é rentável para um exercício único, no entanto.


4

Consegui excluir 19 milhões de linhas da minha tabela de 21 milhões de linhas em questão de minutos . Aqui está a minha abordagem.

Se você tiver uma chave primária de incremento automático nesta tabela, poderá usá-la.

  1. Obtenha o valor mínimo da chave primária da tabela grande em que readTime <dateadd (MONTH, -7, GETDATE ()). (Adicione o índice no readTime, se ainda não estiver presente, esse índice será excluído de qualquer maneira junto com a tabela na etapa 3.). Permite armazená-lo em uma variável 'min_primary'

  2. Insira todas as linhas com chave primária> min_primary em uma tabela intermediária (tabela de memória se o número de linhas não for grande).

  3. Largue a mesa grande.

  4. Recrie a tabela. Copie todas as linhas da tabela intermediária para a tabela principal.

  5. Solte a mesa de preparação.


3

Você pode excluir pequenos lotes usando um loop while, algo como isto:

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

Outro uso:

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

Opcional;

Se o log de transações estiver ativado, desative os logs de transações.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Sintaxe mais curta

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

1

Se você estiver usando o SQL Server 2016 ou superior e se sua tabela estiver tendo partições criadas com base na coluna que você está tentando excluir (por exemplo, coluna Timestamp), poderá usar este novo comando para excluir dados por partições.

TABELA TRUNCATE WITH (PARTITIONS ({|} [, ... n])))

Isso excluirá os dados apenas na (s) partição (ões) selecionada (s) e deve ser a maneira mais eficiente de excluir dados de parte da tabela, uma vez que não criará logs de transações e será feito tão rápido quanto o truncado normal, mas sem que todos os dados sejam excluídos Da mesa.

A desvantagem é que, se sua tabela não estiver configurada com partição, você precisará ir à escola antiga e excluir os dados com abordagem regular e, em seguida, recriar a tabela com partições para que você possa fazer isso no futuro, e foi o que eu fiz. Eu adicionei a criação e exclusão da partição no próprio procedimento de inserção. Eu tinha uma tabela com 500 milhões de linhas, então essa era a única opção para reduzir o tempo de exclusão.

Para obter mais detalhes, consulte os links abaixo: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL Server 2016 Truncar tabela com partições

Abaixo está o que eu fiz primeiro para excluir os dados antes de poder recriar a tabela com partições com os dados necessários. Essa consulta será executada por dias durante a janela de tempo especificada até que os dados sejam excluídos.

: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

Se eu digo sem loop, posso usar a GOTOinstrução para excluir grande quantidade de registros usando o sql server. exa.

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

dessa maneira, você pode excluir uma grande quantidade de dados com um tamanho menor de exclusão.

deixe-me saber se requer mais informações.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.