Temos uma tabela muito grande (100 milhões de linhas) e precisamos atualizar alguns campos nela.
Para envio de logs, etc., também, obviamente, queremos mantê-lo em transações pequenas.
- O abaixo fará o truque?
- E como podemos fazer com que imprima alguma saída, para que possamos ver o progresso? (tentamos adicionar uma instrução PRINT lá, mas nada foi exibido durante o loop while)
O código é:
DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000
UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null
WHILE @@ROWCOUNT > 0
BEGIN
UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null
END
Eu não estava ciente dessa questão quando respondi à pergunta relacionada ( As transações explícitas são necessárias neste loop while? ), Mas, para fins de integridade, abordarei esse problema aqui, pois não fazia parte da minha sugestão nessa resposta vinculada .
Como estou sugerindo agendar isso por meio de um trabalho do SQL Agent (afinal, são 100 milhões de linhas), não acho que qualquer forma de enviar mensagens de status ao cliente (ou seja, SSMS) seja ideal (embora, se isso for sempre uma necessidade de outros projetos, então concordo com Vladimir que usar
RAISERROR('', 10, 1) WITH NOWAIT;
é o caminho a percorrer).Nesse caso específico, eu criaria uma tabela de status que pode ser atualizada a cada loop com o número de linhas atualizadas até o momento. E não custa nada jogar no tempo atual para ter uma batida de coração no processo.
Dado que você deseja cancelar e reiniciar o processo,
Estou cansado de envolver o UPDATE da tabela principal com o UPDATE da tabela de status em uma transação explícita. No entanto, se você sentir que a tabela de status está fora de sincronia devido ao cancelamento, é fácil atualizar com o valor atual simplesmente atualizando-o manualmente com o arquivoe há duas tabelas para atualizar (ou seja, a tabela principal e a tabela de status), devemos usar uma transação explícita para manter essas duas tabelas em sincronia, mas não queremos correr o risco de ter uma transação órfã se você cancelar o processo em um ponto depois de ter iniciado a transação, mas não a confirmou. Isso deve ser seguro desde que você não interrompa o trabalho do SQL Agent.COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
.Como você pode interromper o processo sem, bem, interrompê-lo? Pedindo para parar :-). Sim. Ao enviar um "sinal" ao processo (semelhante ao
kill -3
Unix), você pode solicitar que ele pare no próximo momento conveniente (ou seja, quando não houver nenhuma transação ativa!) e faça com que ele se limpe de maneira agradável e organizada.Como você pode se comunicar com o processo em execução em outra sessão? Usando o mesmo mecanismo que criamos para comunicar seu status atual para você: a tabela de status. Basta adicionar uma coluna que o processo verificará no início de cada loop para saber se deve prosseguir ou abortar. E como a intenção é agendar isso como um trabalho do SQL Agent (executado a cada 10 ou 20 minutos), também devemos verificar logo no início, pois não adianta preencher uma tabela temporária com 1 milhão de linhas se o processo estiver apenas acontecendo para sair um momento depois e não usar nenhum desses dados.
Você pode verificar o status a qualquer momento usando a seguinte consulta:
Deseja pausar o processo, seja em um trabalho do SQL Agent ou até mesmo no SSMS no computador de outra pessoa? Apenas corra:
Quer que o processo seja capaz de começar de novo? Apenas corra:
ATUALIZAR:
Aqui estão algumas coisas adicionais para tentar que podem melhorar o desempenho desta operação. Nenhum é garantido para ajudar, mas provavelmente vale a pena testar. E com 100 milhões de linhas para atualizar, você tem bastante tempo/oportunidade para testar algumas variações ;-).
TOP (@UpdateRows)
à consulta UPDATE para que a linha superior se pareça com:UPDATE TOP (@UpdateRows) ht
Às vezes, ajuda o otimizador a saber quantas linhas no máximo serão afetadas, para que não perca tempo procurando por mais.
Adicione uma PRIMARY KEY à
#CurrentSet
tabela temporária. A ideia aqui é ajudar o otimizador com o JOIN para a tabela de 100 milhões de linhas.E só para deixar claro para não ser ambíguo, não deve haver nenhuma razão para adicionar um PK à
#FullSet
tabela temporária, pois é apenas uma tabela de fila simples onde a ordem é irrelevante.SELECT
que alimenta a#FullSet
tabela temporária. Aqui estão algumas considerações relacionadas à adição de tal índice:WHERE deleted is null or deletedDate is null
SELECT
, prejudicará oUPDATE
, pois é outro objeto que deve ser atualizado durante essa operação, portanto, mais I/O. Isso funciona tanto usando um índice filtrado (que diminui à medida que você atualiza as linhas, pois menos linhas correspondem ao filtro) quanto esperando um pouco para adicionar o índice (se não for muito útil no início, não há razão para incorrer a E/S adicional).Respondendo a segunda parte: como imprimir alguma saída durante o loop.
Eu tenho alguns procedimentos de manutenção de longa duração que o administrador do sistema às vezes precisa executar.
Eu os executo do SSMS e também notei que a
PRINT
instrução é mostrada no SSMS somente após o término de todo o procedimento.Então, estou usando
RAISERROR
com baixa severidade:Estou usando SQL Server 2008 Standard e SSMS 2012 (11.0.3128.0). Aqui está um exemplo de trabalho completo para executar no SSMS:
Quando eu comento
RAISERROR
e deixo apenasPRINT
as mensagens na guia Mensagens no SSMS aparecem somente após o término de todo o lote, após 6 segundos.Quando eu comento
PRINT
e usoRAISERROR
as mensagens na guia Mensagens no SSMS aparecem sem esperar 6 segundos, mas conforme o loop avança.Curiosamente, quando uso ambos
RAISERROR
ePRINT
, vejo ambas as mensagens. Primeiro vem a mensagem do primeiroRAISERROR
, depois atrasa por 2 segundos, depois primeiroPRINT
e segundoRAISERROR
e assim por diante.Em outros casos, uso uma tabela dedicada separada
log
e simplesmente insiro uma linha na tabela com algumas informações que descrevem o estado atual e o carimbo de data/hora do processo de execução longa.Enquanto o longo processo é executado, eu periodicamente
SELECT
dalog
mesa para ver o que está acontecendo.Isso obviamente tem certa sobrecarga, mas deixa um log (ou histórico de logs) que posso examinar em meu próprio ritmo mais tarde.
Você pode monitorá-lo de outra conexão com algo como:
para ver quanto falta fazer. Isso pode ser útil se um aplicativo estiver chamando o processo, em vez de executá-lo manualmente no SSMS ou similar, e precisar mostrar o progresso: execute o processo principal de forma assíncrona (ou em outro thread) e, em seguida, faça um loop chamando o "quanto resta " verifique de vez em quando até que a chamada assíncrona (ou thread) seja concluída.
Definir o nível de isolamento o mais frouxo possível significa que ele deve retornar em um tempo razoável sem ficar preso atrás da transação principal devido a problemas de bloqueio. Isso pode significar que o valor retornado é um pouco impreciso, é claro, mas como um simples medidor de progresso, isso não deve importar.