Eu tenho que excluir mais de 16 milhões de registros de uma tabela de mais de 221 milhões e está indo muito devagar.
Agradeço se você compartilhar sugestões para tornar o código abaixo mais rápido:
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
Plano de execução (limitado a 2 iterações)
VendorId
é PK e não clusterizado , em que o índice clusterizado não está sendo usado por esse script. Existem outros 5 índices não exclusivos e sem cluster.
A tarefa é "remover fornecedores que não existem em outra tabela" e fazer backup deles em outra tabela. Eu tenho 3 mesas vendors, SpecialVendors, SpecialVendorBackups
. Tentando remover o SpecialVendors
que não existe na Vendors
tabela e ter um backup dos registros excluídos, caso o que estou fazendo esteja errado e precise devolvê-los em uma semana ou duas.
sql-server
query-performance
delete
cilerler
fonte
fonte
Respostas:
O plano de execução mostra que ele está lendo linhas de um índice não clusterizado em alguma ordem e, em seguida, realiza pesquisas para cada linha externa lida para avaliar o
NOT EXISTS
Você está excluindo 7,2% da tabela. 16.000.000 linhas em 3.556 lotes de 4.500
Supondo que as linhas que se qualificam sejam distribuídas de forma constante por todo o índice, isso significa que ele excluirá aproximadamente 1 linha a cada 13,8 linhas.
Portanto, a iteração 1 lê 62.156 linhas e executa muitas buscas de índice antes de encontrar 4.500 para excluir.
a iteração 2 lerá 57.656 (62.156 - 4.500) linhas que definitivamente não se qualificarão, ignorando atualizações simultâneas (como já foram processadas) e depois outras 62.156 linhas para obter 4.500 para excluir.
a iteração 3 lerá (2 * 57.656) + 62.156 linhas e assim por diante até que finalmente a iteração 3.556 lerá (3.555 * 57.656) + 62.156 linhas e executará muitas buscas.
Portanto, o número de pesquisas de índice realizadas em todos os lotes é
SUM(1, 2, ..., 3554, 3555) * 57,656 + (3556 * 62156)
Qual é
((3555 * 3556 / 2) * 57656) + (3556 * 62156)
- ou364,652,494,976
Sugiro que você materialize as linhas para excluir primeiro uma tabela temporária
E altere
DELETE
para excluirWHERE PK IN (SELECT PK FROM #MyTempTable WHERE BatchNumber = @BatchNumber)
Você ainda pode precisar incluir umNOT EXISTS
naDELETE
própria consulta para atender às atualizações, pois a tabela temporária foi preenchida, mas isso deve ser muito mais eficiente, pois será necessário realizar 4.500 buscas por lote.fonte
PK
coluna? (Eu acredito que você está me sugerindo para mover aqueles a tabela temporária completamente, mas queria verifique)DELETE TOP (@BATCHSIZE) FROM MySourceTable
deve apenas serDELETE FROM MySourceTable
também indexar a tabela temporáriaCREATE TABLE #MyTempTable ( Id BIGINT, BatchNumber BIGINT, PRIMARY KEY(BatchNumber, Id) );
e éVendorId
definitivamente o PK por conta própria? Você tem> 221 milhões de fornecedores diferentes?O plano de execução sugere que cada loop sucessivo fará mais trabalho que o loop anterior. Supondo que as linhas a serem excluídas sejam distribuídas igualmente por toda a tabela, o primeiro loop precisará varrer cerca de 4500 * 221000000/16000000 = 62156 linhas para encontrar 4500 linhas a serem excluídas. Ele também fará o mesmo número de pesquisas de índice em cluster na
vendor
tabela. No entanto, o segundo loop precisará ler além das mesmas linhas 62156 - 4500 = 57656 que você não excluiu da primeira vez. Podemos esperar que o segundo loop verifique 120000 linhas deMySourceTable
e faça 120000 buscas navendor
tabela. A quantidade de trabalho necessária por loop aumenta a uma taxa linear. Como uma aproximação, podemos dizer que o loop médio precisará ler 102516868 linhas deMySourceTable
e para fazer 102516868 buscas em relação aovendor
mesa. Para excluir 16 milhões de linhas com um tamanho de lote de 4500, seu código precisa executar 16000000/4500 = 3556 loops, para que a quantidade total de trabalho para seu código seja concluída em torno de 364,5 bilhões de linhas lidasMySourceTable
e 364,5 bilhões de pesquisas de índice.Um problema menor é que você usa uma variável local
@BATCHSIZE
em uma expressão TOP sem umaRECOMPILE
ou outra dica. O otimizador de consulta não saberá o valor dessa variável local ao criar um plano. Ele assumirá que é igual a 100. Na realidade, você está excluindo 4500 linhas em vez de 100 e pode acabar com um plano menos eficiente devido a essa discrepância. A estimativa de baixa cardinalidade ao inserir em uma tabela também pode causar um impacto no desempenho. O SQL Server pode escolher uma API interna diferente para inserir, se achar que precisa inserir 100 linhas, em vez de 4500 linhas.Uma alternativa é simplesmente inserir as chaves primárias / chaves em cluster das linhas que você deseja excluir em uma tabela temporária. Dependendo do tamanho das suas colunas-chave, isso pode caber facilmente no tempdb. Nesse caso, você pode obter um registro mínimo, o que significa que o log de transações não explodirá. Você também pode obter log mínimo em qualquer banco de dados com um modelo de recuperação
SIMPLE
. Consulte o link para obter mais informações sobre os requisitos.Se isso não for uma opção, altere seu código para aproveitar o índice em cluster
MySourceTable
. O importante é escrever seu código para que você faça aproximadamente a mesma quantidade de trabalho por loop. Você pode fazer isso aproveitando o índice em vez de apenas verificar a tabela desde o início de cada vez. Eu escrevi um post de blog que aborda alguns métodos diferentes de loop. Os exemplos nesse post são inseridos em uma tabela em vez de exclusões, mas você deve conseguir adaptar o código.No código de exemplo abaixo, assumo que a chave primária e a chave em cluster do seu
MySourceTable
. Escrevi esse código rapidamente e não consigo testá-lo:A parte principal está aqui:
Cada loop lê apenas 60000 linhas de
MySourceTable
. Isso deve resultar em um tamanho médio de exclusão de 4500 linhas por transação e um tamanho máximo de exclusão de 60000 linhas por transação. Se você quer ser mais conservador com um tamanho de lote menor, isso também é bom. A@STARTID
variável avança após cada loop, para evitar a leitura da mesma linha mais de uma vez na tabela de origem.fonte
Dois pensamentos vêm à mente:
O atraso provavelmente se deve à indexação com esse volume de dados. Tente soltar os índices, excluir e recriar os índices.
Ou..
Pode ser mais rápido copiar as linhas que você deseja manter em uma tabela temporária, soltar a tabela com 16 milhões de linhas e renomear a tabela temporária (ou copiar para uma nova instância da tabela de origem).
fonte