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 de MySourceTable
e faça 120000 buscas na vendor
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 de MySourceTable
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 lidas MySourceTable
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 uma RECOMPILE
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:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @BATCHSIZE INT,
@ITERATION INT,
@TOTALROWS INT,
@MSG VARCHAR(500)
@STARTID BIGINT,
@NEXTID BIGINT;
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;
SELECT @STARTID = ID
FROM MySourceTable
ORDER BY ID
OFFSET 0 ROWS
FETCH FIRST 1 ROW ONLY;
SELECT @NEXTID = ID
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
OFFSET (60000) ROWS
FETCH FIRST 1 ROW ONLY;
BEGIN TRY
BEGIN TRANSACTION;
WHILE @STARTID IS NOT NULL
BEGIN
WITH MySourceTable_DELCTE AS (
SELECT TOP (60000) *
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
)
DELETE FROM MySourceTable_DELCTE
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;
SET @STARTID = @NEXTID;
SET @NEXTID = NULL;
SELECT @NEXTID = ID
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
OFFSET (60000) ROWS
FETCH FIRST 1 ROW ONLY;
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
A parte principal está aqui:
WITH MySourceTable_DELCTE AS (
SELECT TOP (60000) *
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
)
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.