Se eu tiver uma UPDATE
declaração que na verdade não altera nenhum dado (porque os dados já estão no estado atualizado). Existe algum benefício de desempenho em colocar uma verificação na WHERE
cláusula para evitar a atualização?
Por exemplo, haveria alguma diferença na velocidade de execução entre UPDATE 1 e UPDATE 2 no seguinte:
CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
(1, 1),
(2, 2),
(3, 3);
-- UPDATE 1
UPDATE MyTable
SET
Value = 2
WHERE
ID = 2
AND Value <> 2;
SELECT @@ROWCOUNT;
-- UPDATE 2
UPDATE MyTable
SET
Value = 2
WHERE
ID = 2;
SELECT @@ROWCOUNT;
DROP TABLE MyTable;
O motivo pelo qual pergunto é que preciso que a contagem de linhas inclua a linha inalterada para saber se devo fazer uma inserção se o ID não existir. Como tal, usei o formulário UPDATE 2. Se houver um benefício de desempenho ao usar o formulário UPDATE 1, é possível obter a contagem de linhas que preciso de alguma forma?
Certamente pode haver, pois há uma pequena diferença de desempenho devido à UPDATE 1 :
No entanto, quanta diferença existe precisaria ser medida por você em seu sistema com seu esquema, dados e carga do sistema. Existem vários fatores que influenciam o impacto de uma atualização sem atualização:
UPDATE TableName SET Field1 = Field1
, um Update Trigger será acionado e indicará que o campo foi atualizado (se você verificar usando as funções UPDATE() ou COLUMNS_UPDATED ) e que o campo nas tabelasINSERTED
e são o mesmo valor.DELETED
Além disso, a seguinte seção de resumo é encontrada no artigo de Paul White, The Impact of Non-Updating Updates (conforme observado por @spaghettidba em um comentário em sua resposta):
Por favor, tenha em mente (especialmente se você não seguir o link para ver o artigo completo de Paul), os dois itens a seguir:
As atualizações não atualizadas ainda têm alguma atividade de log, mostrando que uma transação está começando e terminando. É só que nenhuma modificação de dados acontece (o que ainda é uma boa economia).
Como eu disse acima, você precisa testar em seu sistema. Use as mesmas consultas de pesquisa que Paul está usando e veja se obtém os mesmos resultados. Estou vendo resultados ligeiramente diferentes no meu sistema do que é mostrado no artigo. Ainda não há páginas sujas a serem escritas, mas um pouco mais de atividade de log.
De forma simplista, se você estiver lidando apenas com uma única linha, poderá fazer o seguinte:
Para várias linhas, você pode obter as informações necessárias para tomar essa decisão usando a
OUTPUT
cláusula. Ao capturar exatamente quais linhas foram atualizadas, você pode restringir os itens a serem pesquisados para saber a diferença entre não atualizar as linhas que não existem e não atualizar as linhas que existem, mas não precisam da atualização.Eu mostro a implementação básica na seguinte resposta:
Como evitar o uso da consulta Merge ao upserting vários dados usando o parâmetro xml?
O método mostrado nessa resposta não filtra as linhas que existem ainda não precisam ser atualizadas. Essa parte pode ser adicionada, mas primeiro você precisa mostrar exatamente onde está obtendo o conjunto de dados no qual está mesclando
MyTable
. Eles estão vindo de uma mesa temporária? Um parâmetro com valor de tabela (TVP)?ATUALIZAÇÃO 1:
Finalmente consegui fazer alguns testes e aqui está o que encontrei em relação ao log de transações e bloqueio. Primeiro, o esquema para a tabela:
Em seguida, o teste atualizando o campo para o valor que ele já possui:
Resultados:
Por fim, o teste que filtra a atualização devido ao valor não mudar:
Resultados:
Como você pode ver, nada é gravado no log de transações ao filtrar a linha, ao contrário das duas entradas que marcam o início e o fim da transação. E embora seja verdade que essas duas entradas não são quase nada, elas ainda são alguma coisa.
Além disso, o bloqueio dos recursos PAGE e KEY é menos restritivo ao filtrar as linhas que não foram alteradas. Se nenhum outro processo estiver interagindo com esta tabela, provavelmente não será um problema (mas qual é a probabilidade disso, realmente?). Lembre-se de que esse teste mostrado em qualquer um dos blogs vinculados (e até mesmo meus testes) pressupõe implicitamente que não há contenção na tabela, pois nunca faz parte dos testes. Dizer que as atualizações sem atualização são tão leves que não vale a pena fazer a filtragem precisa ser tomada com um grão de sal, já que o teste foi feito, mais ou menos, no vácuo. Mas em Produção, essa tabela provavelmente não é isolada. Claro, pode muito bem ser que o pouco de registro e bloqueios mais restritivos não se traduzam em menos eficiência. Então, a fonte de informação mais confiável para responder a essa pergunta? Servidor SQL. Especificamente:seu SQL Server. Ele mostrará qual método é melhor para o seu sistema :-).
ATUALIZAÇÃO 2:
Se as operações em que o novo valor é igual ao valor atual (ou seja, sem atualização) numeram as operações em que o novo valor é diferente e a atualização é necessária, o padrão a seguir pode ser ainda melhor, especialmente se há muita disputa na mesa. A ideia é fazer um
SELECT
primeiro simples para obter o valor atual. Se você não obtiver um valor, terá sua resposta em relação aoINSERT
. Se você tiver um valor, poderá fazer um simplesIF
e emitir oUPDATE
somente se for necessário.Resultados:
Portanto, existem apenas 2 bloqueios adquiridos em vez de 3, e ambos os bloqueios são Intent Shared, não Intent eXclusive ou Intent Update ( Lock Compatibility ). Lembrando que cada lock adquirido também será liberado, cada lock é na verdade 2 operações, então esse novo método é um total de 4 operações ao invés das 6 operações do método originalmente proposto. Considerando que esta operação está sendo executada uma vez a cada 15 ms (aproximadamente, conforme declarado pelo OP), ou seja, cerca de 66 vezes por segundo. Portanto, a proposta original equivale a 396 operações de bloqueio/desbloqueio por segundo, enquanto esse novo método equivale a apenas 264 operações de bloqueio/desbloqueio por segundo de bloqueios ainda mais leves. Isso não é garantia de desempenho incrível, mas certamente vale a pena testar :-).
Diminua um pouco o zoom e pense no quadro maior. No mundo real, sua declaração de atualização realmente ficará assim:
Ou vai ficar mais assim:
Porque no mundo real, as tabelas têm muitas colunas. Isso significa que você terá que gerar muita lógica de aplicativo dinâmica complexa para criar strings dinâmicas, OU terá que especificar o conteúdo anterior e posterior de cada campo, sempre.
Se você construir essas instruções de atualização dinamicamente para cada tabela, passando apenas os campos que estão sendo atualizados, você pode rapidamente se deparar com um problema de poluição de cache de plano semelhante ao problema de tamanhos de parâmetro NHibernate de alguns anos atrás. Pior ainda, se você criar as instruções de atualização no SQL Server (como nos procedimentos armazenados), queimará ciclos de CPU preciosos porque o SQL Server não é muito eficiente na concatenação de strings em escala.
Por causa dessas complexidades, geralmente não faz sentido fazer esse tipo de comparação linha por linha, campo por campo, enquanto você faz as atualizações. Em vez disso, pense em operações baseadas em conjuntos.
Você pode ver um ganho de desempenho ao pular linhas que não precisam ser atualizadas apenas quando o número de linhas é grande (menos logs, menos páginas sujas para gravar no disco).
Ao lidar com atualizações de linha única, como no seu caso, a diferença de desempenho é completamente insignificante. Se atualizar as linhas em todos os casos facilitar para você, faça-o.
Para obter mais informações sobre o tópico, consulte Atualizações não atualizadas de Paul White
Você pode combinar a atualização e inserir em uma instrução. No SQL Server, você pode usar uma instrução MERGE para atualizar e inserir, caso não seja encontrada. Para MySQL, você pode usar INSERT ON DUPLICATE KEY UPDATE .
É comum filtrar atualizações não atualizadas, pois isso afetaria gatilhos de trilha de auditoria ou colunas de auditoria como LastModifiedDateTime. A maneira mais fácil de fazer isso para várias colunas que representam NULL é usar EXCEPT
Atualizar apenas se for diferente usando EXCETO