-
Você não deve atualizar 10 mil linhas em um conjunto, a menos que tenha certeza de que a operação está obtendo bloqueios de página (devido a várias linhas por página serem parte doUPDATE
Operação). O problema é que o escalonamento de bloqueio (de bloqueios de linha ou página para tabela) ocorre em 5.000 bloqueios . Portanto, é mais seguro mantê-lo abaixo de 5000, caso a operação esteja usando Row Locks.
-
Você não estar usando SET ROWCOUNT para limitar o número de linhas que serão modificadas. Há duas questões aqui:
-
Ele foi preterido desde que o SQL Server 2005 foi lançado (11 anos atrás):
O uso de SET ROWCOUNT não afetará as instruções DELETE, INSERT e UPDATE em uma versão futura do SQL Server. Evite usar SET ROWCOUNT com instruções DELETE, INSERT e UPDATE em novos trabalhos de desenvolvimento e planeje modificar os aplicativos que o utilizam atualmente. Para um comportamento semelhante, use a sintaxe TOP
-
Pode afetar mais do que apenas a declaração com a qual você está lidando:
Definir a opção SET ROWCOUNT faz com que a maioria das instruções Transact-SQL parem de processar quando forem afetadas pelo número especificado de linhas. Isso inclui gatilhos. A opção ROWCOUNT não afeta cursores dinâmicos, mas limita o conjunto de linhas de cursores de conjunto de chaves e insensíveis. Esta opção deve ser usada com cautela.
Em vez disso, use oTOP ()
cláusula.
-
-
Não há nenhum propósito em ter uma transação explícita aqui. Isso complica o código e você não tem como lidar com umROLLBACK
, o que nem é necessário, pois cada instrução é sua própria transação (ou seja, auto-commit).
-
Supondo que você encontre uma razão para manter a transação explícita, então você não tem umTRY
/CATCH
estrutura. Por favor, veja minha resposta no DBA.StackExchange para umTRY
/CATCH
modelo que lida com transações:
Somos obrigados a lidar com a transação no código C #, bem como no procedimento da loja
Eu suspeito que o verdadeiro
WHERE
cláusula não está sendo mostrada no código de exemplo na pergunta, então simplesmente confiando no que foi mostrado, um melhor modelo seria:DECLARE @Rows INT,
@BatchSize INT; -- keep below 5000 to be safe
SET @BatchSize = 2000;
SET @Rows = @BatchSize; -- initialize just to enter the loop
BEGIN TRY
WHILE (@Rows = @BatchSize)
BEGIN
UPDATE TOP (@BatchSize) tab
SET tab.Value = 'abc1'
FROM TableName tab
WHERE tab.Parameter1 = 'abc'
AND tab.Parameter2 = 123
AND tab.Value <> 'abc1' COLLATE Latin1_General_100_BIN2;
-- Use a binary Collation (ending in _BIN2, not _BIN) to make sure
-- that you don't skip differences that compare the same due to
-- insensitivity of case, accent, etc, or linguistic equivalence.
SET @Rows = @@ROWCOUNT;
END;
END TRY
BEGIN CATCH
RAISERROR(stuff);
RETURN;
END CATCH;
Ao testar
@Rows
contra @BatchSize
, você pode evitar esse UPDATE
final query (na maioria dos casos) porque o conjunto final normalmente é um número de linhas menor que @BatchSize
, caso em que sabemos que não há mais para processar (que é o que você vê na saída mostrada em sua resposta). Apenas nos casos em que o conjunto final de linhas é igual a @BatchSize
este código executará um UPDATE
final afetando 0 linhas. Eu também adicionei uma condição ao
WHERE
cláusula para evitar que as linhas que já foram atualizadas sejam atualizadas novamente. OBSERVAÇÃO SOBRE O DESEMPENHO
Eu enfatizei "melhor" acima (como em "este é um melhor model") porque isso tem várias melhorias em relação ao código original do O.P. e funciona bem em muitos casos, mas não é perfeito para todos os casos. Para tabelas de pelo menos um determinado tamanho (que varia devido a vários fatores, para ser mais específico), o desempenho será degradado, pois há menos linhas para corrigir se:
- não há índice para dar suporte à consulta ou
- há um índice, mas pelo menos uma coluna no
WHERE
cláusula é um tipo de dados string que não usa um agrupamento binário, portanto, umCOLLATE
é adicionada à consulta aqui para forçar o agrupamento binário, e isso invalida o índice (para essa consulta específica).
Esta é a situação que a @mikesigs encontrou, exigindo assim uma abordagem diferente. O método atualizado copia os IDs de todas as linhas a serem atualizadas em uma tabela temporária e, em seguida, usa essa tabela temporária para
INNER JOIN
para a tabela que está sendo atualizada na(s) coluna(s) de chave de índice clusterizado. (É importante capturar e ingressar no índice clusterizado colunas, sejam ou não as colunas de chave primária!). Por favor, veja a resposta @mikesigs abaixo para mais detalhes. A abordagem mostrada nessa resposta é um padrão muito eficaz que eu mesmo usei em muitas ocasiões. As únicas mudanças que eu faria são:
- Crie explicitamente os
#targetIds
tabela em vez de usarSELECT INTO...
- Para os
#targetIds
table, declare uma chave primária clusterizada na(s) coluna(s). - Para os
#batchIds
table, declare uma chave primária clusterizada na(s) coluna(s). - Para inserir em
#targetIds
, useINSERT INTO #targetIds (column_name(s)) SELECT
e remova oORDER BY
pois é desnecessário.
Portanto, se você não tiver um índice que possa ser usado para esta operação e não puder criar temporariamente um que realmente funcione (um índice filtrado pode funcionar, dependendo do seu
WHERE
cláusula para o UPDATE
query), tente a abordagem mostrada na resposta @mikesigs (e se você usar essa solução, vote nela).