Instruções individuais - DML, DDL, etc - são transações em si mesmas. Então, sim, após cada iteração do loop (tecnicamente: após cada instrução), qualquer que seja a UPDATE
alteração dessa declaração foi confirmada automaticamente.
Claro, sempre há uma exceção, certo? É possível ativar transações implícitas via SET IMPLICIT_TRANSACTIONS ; nesse caso, a primeira UPDATE
instrução iniciaria uma transação que você precisaria para COMMIT
ou ROLLBACK
no final. Essa é uma configuração no nível da sessão que está desativada por padrão na maioria dos casos.
precisamos adicionar instruções explícitas de BEGIN TRANSACTION / END TRANSACTION para que possamos cancelar a qualquer momento?
Não. E, de fato, considerando que você deseja interromper o processo e reiniciar, adicionar uma transação explícita (ou ativar transações implícitas) seria uma má ideia, pois interromper o processo pode pegá-lo antes de ele fazer o COMMIT
. Nesse caso, você precisaria emitir manualmente o COMMIT
(se você estiver no SSMS) ou se estiver executando isso em um trabalho do SQL Agent, não terá essa oportunidade e poderá acabar com uma transação órfã.
Além disso, convém definir @CHUNK_SIZE
um número menor. A escalação de bloqueios geralmente ocorre em 5000 bloqueios adquiridos em um único objeto. Dependendo do tamanho das linhas e se estiver fazendo bloqueios de linha versus bloqueios de página, você pode estar ultrapassando esse limite. Se o tamanho de uma linha for de tal forma que apenas 1 ou 2 linhas se ajustem a cada página, você poderá sempre acessá-lo, mesmo que esteja bloqueando a página.
Se a tabela estiver particionada, você poderá definir a LOCK_ESCALATION
opção (introduzida no SQL Server 2008) para que a tabela AUTO
bloqueie apenas a partição e não a tabela inteira ao ser escalada. Ou, para qualquer tabela, você pode definir a mesma opção DISABLE
, embora tenha que ter muito cuidado com isso. Veja ALTER TABLE para detalhes.
Aqui está uma documentação que fala sobre o escalonamento de bloqueios e os limites: Escalonamento de bloqueios (diz se aplica ao "SQL Server 2008 R2 e versões posteriores"). E aqui está uma postagem de blog que trata da detecção e correção da escalação de bloqueios: Bloqueio no Microsoft SQL Server (Parte 12 - Escalação de Bloqueios) .
Independentemente da pergunta exata, mas relacionada à consulta na pergunta, existem algumas melhorias que podem ser feitas aqui (ou pelo menos parece assim apenas ao olhar para ela):
Para o seu loop, fazer WHILE (@@ROWCOUNT = @CHUNK_SIZE)
é um pouco melhor, pois se o número de linhas atualizadas na última iteração for menor que o valor solicitado para UPDATE, não haverá trabalho a ser feito.
Se o deleted
campo é um BIT
tipo de dados, então não é que o valor determinado por se ou não deletedDate
é 2000-01-01
? Por que você precisa dos dois?
Se esses dois campos são novos e você os adicionou NULL
, pode ser uma operação on-line / sem bloqueio e agora deseja atualizá-los para o valor "padrão", então isso não era necessário. A partir do SQL Server 2012 (apenas Enterprise Edition), a adição de NOT NULL
colunas com restrição DEFAULT é uma operação sem bloqueio, desde que o valor de DEFAULT seja constante. Portanto, se você ainda não estiver usando os campos, basta soltar e adicionar novamente como NOT NULL
e com uma restrição DEFAULT.
Se nenhum outro processo estiver atualizando esses campos enquanto você estiver fazendo essa atualização, será mais rápido se você enfileirar os registros que deseja atualizar e apenas trabalhar nessa fila. Há um problema de desempenho no método atual, pois é necessário consultar novamente a tabela a cada vez para obter o conjunto que precisa ser alterado. Em vez disso, você pode fazer o seguinte, que varre a tabela apenas uma vez nesses dois campos e emite apenas instruções UPDATE muito direcionadas. Também não há penalidade de interromper o processo a qualquer momento e iniciá-lo mais tarde, pois a população inicial da fila simplesmente encontrará os registros restantes para atualização.
- Crie uma tabela temporária (#FullSet) que possui apenas os campos-chave do índice clusterizado.
- Crie uma segunda tabela temporária (#CurrentSet) dessa mesma estrutura.
inserir em #FullSet via SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;
O TOP(n)
está lá devido ao tamanho da tabela. Com 100 milhões de linhas na tabela, você realmente não precisa preencher a tabela de filas com todo esse conjunto de chaves, especialmente se planeja interromper o processo de vez em quando e reiniciá-lo mais tarde. Então, talvez seja definido n
como 1 milhão e deixe isso terminar até o fim. Você sempre pode agendar isso em um trabalho do SQL Agent que execute o conjunto de 1 milhão (ou talvez até menos) e aguarde o próximo horário agendado para recuperar novamente. Em seguida, você pode agendar a execução a cada 20 minutos, para que haja algum espaço de respiração forçado entre os conjuntos de n
, mas ele ainda concluirá todo o processo sem supervisão. Depois, basta que o trabalho se exclua quando não houver mais nada a fazer :-).
- em um loop, faça:
- Preencha o lote atual por meio de algo como
DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
IF (@@ROWCOUNT = 0) BREAK;
- Faça o UPDATE usando algo como:
UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
- Limpe o conjunto atual:
TRUNCATE TABLE #CurrentSet;
- Em alguns casos, ajuda a adicionar um Índice Filtrado para ajudar os
SELECT
que são alimentados na #FullSet
tabela temporária. Aqui estão algumas considerações relacionadas à adição desse índice:
- A condição WHERE deve corresponder à condição WHERE da sua consulta, portanto
WHERE deleted is null or deletedDate is null
- No início do processo, a maioria das linhas corresponderá à sua condição WHERE, portanto, um índice não é tão útil. Você pode esperar até algo em torno da marca de 50% antes de adicionar isso. Obviamente, quanto ajuda e quando é melhor adicionar o índice variam devido a vários fatores, por isso é um pouco de tentativa e erro.
- Pode ser necessário atualizar manualmente STATS e / ou RECONSTRUIR o índice para mantê-lo atualizado, pois os dados base estão mudando com bastante frequência
- Lembre-se de que o índice, enquanto ajuda o
SELECT
, prejudicará o, UPDATE
pois é outro objeto que deve ser atualizado durante essa operação, portanto, mais E / S. Isso funciona tanto no uso de um Índice Filtrado (que diminui à medida que você atualiza as linhas, pois menos linhas correspondem ao filtro) quanto na espera de adicionar um índice (se não for de grande ajuda no início, não há razão para incorrer). E / S adicional).
ATUALIZAÇÃO: Por favor, veja minha resposta a uma pergunta relacionada a esta questão para a implementação completa do que é sugerido acima, incluindo um mecanismo para rastrear o status e cancelar de forma limpa: servidor sql: atualizando campos em uma tabela enorme em pequenos pedaços: como obter status de progresso?