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())
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())
Respostas:
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.
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.
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.
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).
optimal solution for unknown case
esse é 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.
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
COMMIT TRANSACTION
e CHECKPOINT
os logs ainda estão crescendo. Obrigado por deixar isso claro.
@Deleted_Rows
com 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.
WHILE
próprio loop: dateadd(MONTH,-7,GETDATE())
.
WHILE
loop.
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
GO xx
deve funcionar? Eu recebo o erro "Não foi possível encontrar o procedimento armazenado ''" . Sem o GO
comando, ele funciona bem.
@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
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
# of rows
para excluir de cada vez, e também a WHERE
cláusula. Funciona como um encanto!
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.
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.
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'
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).
Largue a mesa grande.
Recrie a tabela. Copie todas as linhas da tabela intermediária para a tabela principal.
Solte a mesa de preparação.
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
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;
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()
Se eu digo sem loop, posso usar a GOTO
instruçã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.