Estou tentando realizar uma operação de limpeza em uma tabela usando DELETE
e recebo o seguinte erro:
Não foi possível alocar espaço para armazenamento temporário de execução do objeto 'dbo.SORT: 140767697436672' no banco de dados 'tempdb' porque o grupo de arquivos 'PRIMARY' está cheio. Crie espaço em disco excluindo arquivos desnecessários, descartando objetos no grupo de arquivos, adicionando arquivos adicionais ao grupo de arquivos ou definindo o crescimento automático para arquivos existentes no grupo de arquivos.
Antes de executar o DELETE
tenho mais de 11G de espaço livre em disco. Quando o erro é emitido, não tenho quase nada nessa partição. As informações de contexto estão abaixo:
1) Consulta problemática:
declare @deleteDate DATETIME2 = DATEADD(month, -3, GETDATE())
delete from art.ArticleConcept where ArticleId IN (select ArticleId from art.Article where PublishDate < @deleteDate)
2) Cardinalidade para tabelas envolvidas
declare @deleteDate DATETIME2 = DATEADD(month, -3, GETDATE())
select count(1) from art.Article -- 137181
select count(1) from art.Article where PublishDate < @deleteDate -- 111450
select count(1) from art.ArticleConcept where ArticleId IN (select ArticleId from art.Article where PublishDate < @deleteDate) -- 12153045
exec sp_spaceused 'art.ArticleConcept'
-- name rows reserved data index_size unused
-- ArticleConcept 14624589 1702000 KB 616488 KB 1084272 KB 1240 KB
3) Índices
-- index_name index_description index_keys
-- IDX_ArticleConcept_ArticleId_Incl_LexemId_Freq nonclustered located on PRIMARY ArticleId
CREATE NONCLUSTERED INDEX [IDX_ArticleConcept_ArticleId_Incl_LexemId_Freq] ON [art].[ArticleConcept]
(
[ArticleId] ASC
)
INCLUDE ( [LexemId],
[Freq]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
4) Servidor
Select @@version
-- Microsoft SQL Server 2014 - 12.0.2000.8 (X64)
-- Feb 20 2014 20:04:26
-- Copyright (c) Microsoft Corporation
-- Express Edition (64-bit) on Windows NT 6.3 <X64> (Build 9600: ) (Hypervisor)
5) Plano de execução (estimado)
Entendo que estou executando um DELETE grande, mas não consigo entender por que isso requer tanto espaço: a ArticleConcept
tabela inteira tem menos de 2 GB (espaço reservado), mas para remover registros dela é necessário mais de 11 GB.
Pergunta: Por que meu comando DELETE requer uma grande quantidade de armazenamento temporário de execução?
Eu removi todos os índices secundários e pude executar o DELETE
. No entanto, por que é necessário tanto mais espaço para realizar as tarefas DELETE
ao tê-las, parece-me estranho.
Estou tentando excluir 12.153.045 de 14.624.589 registros (muitos). Não monitorei o log de transações, mas uma vez recebi um erro relacionado a ele:
O log de transações do banco de dados... está cheio devido a 'ACTIVE_TRANSACTION'
Há sete operadores no plano de consulta que podem se espalhar para tempdb. Eu os numerei abaixo:
A subconsulta
select ArticleId from art.Article where PublishDate < @deleteDate
foi implementada como junção entre dois índices não clusterizados pelo otimizador de consulta. A junção é uma junção de hash que requer que uma tabela de hash seja construída no rótulo 1 . É possível que a tabela de hash seja derramada no tempdb. Para sua consulta, a tabela de hash tem apenas cerca de 100 mil linhas, portanto, não é provável que seja o problema.A junção entre
ArticleConcept
eArticle
é implementada como uma junção de mesclagem. Ambas as entradas de junção precisam ser classificadas para a junção que resulta na classificação vista no rótulo 2 . Essa classificação só precisa processar cerca de 100 mil linhas.Uma classificação é feita no rótulo 3 para melhorar o desempenho da exclusão. Os dados serão classificados na ordem das chaves do índice clusterizado da tabela. Você está excluindo cerca de 12 milhões de linhas, então espero que classifique as chaves agrupadas de 12 milhões de linhas. Isso pode se espalhar para o tempdb.
A tabela de destino da exclusão possui índices não clusterizados. O otimizador de consulta tem alguns métodos diferentes para implementar as atualizações nos índices. Ele escolhe uma atualização ampla por índice . Isso é feito com base no custo e provavelmente ocorre porque você está excluindo uma grande porcentagem de linhas da tabela de destino. O spool de tabela no rótulo 4 contém todas as chaves de índice junto com as chaves de índice clusterizadas. Ele armazenará 12 milhões de linhas e gravará em tempdb.
As classificações nos rótulos 5 , 6 e 7 são para classificar os dados na ordem das chaves de índice e das chaves de índice clusterizado de cada índice não clusterizado. É provável que esses tipos estejam vazando para o tempdb.
Todos esses derramamentos se somam. Se você tiver um tipo de 1 GB de dados no disco e esse tipo de derramamento no disco, ele não consumirá necessariamente exatamente 1 GB de espaço tempdb. Na minha experiência, geralmente requer mais espaço no tempdb do que no disco.
Mesmo que a consulta não tenha falhado, ainda não é a abordagem mais ideal. A exclusão de 12 milhões de linhas de uma tabela de 14 milhões de linhas do índice clusterizado e três índices não clusterizados é muito trabalhoso. Seria mais eficiente inserir as linhas para manter em outra tabela, construir os índices não clusterizados nessa tabela e alternar as tabelas no lugar. Como você mesmo viu, descartar os índices não clusterizados antes da exclusão e recriá-los após a exclusão pode ser suficiente. As soluções alternativas descritas aqui só devem ser feitas durante uma janela de manutenção quando os usuários finais não estiverem acessando os dados.
Você precisa agrupar suas exclusões para não bloquear a tabela e preencher seu log de transações.
Você pode experimentar tamanhos de lote maiores (geralmente 5000-50000), mas este é um bom lugar para começar. Você precisa ter cuidado com as tentativas de escalonamento de bloqueio ao escolher tamanhos de lote.
Você provavelmente também verá uma melhoria enviando sua lista de
ArticleId
s para uma tabela temporária para que você só precise digitalizarart.Article
uma vez.O principal problema é que não há coluna de data no
art.ArticleConcept
.Além disso, você deve sempre compartilhar a estrutura da tabela de ambas as tabelas.
Excluir Bacthwise também é bom.
Acho que @Joe sugeriu o mesmo método abaixo
A principal vantagem da abordagem acima é que você não precisa recuperar espaço ou reconstruir o índice ou reconstruir as estatísticas.
Se
Select * into
a parte não for problemática, vá em frente.Além disso, você tem que criar
Covering index
naart.ArticleConcept
tabela.