Pergunta : No SQL Server 2016, atualizar uma coluna com o mesmo valor (por exemplo, atualizar uma coluna de 'john'
to 'john'
) produz a mesma quantidade de log de transações que ao atualizar uma coluna com um valor diferente? Leia abaixo para mais detalhes.
Temos vários trabalhos do SQL Agent em execução em uma programação. Esses trabalhos selecionam dados de uma tabela de origem (dados replicados, servidores vinculados), transformam-nos e inserem/atualizam/excluem as linhas da tabela de destino local de acordo.
Passamos por várias estratégias enquanto tentamos encontrar a melhor maneira de conseguir isso. Nós tentamos
- Atualizando destino da origem usando MERGE
- Atualizando o destino da origem usando UPDATE para atualizar todas as colunas
- Atualizando o destino da origem usando uma única instrução UPDATE por coluna de destino
Agora, sou apenas um DBD júnior e meu entendimento de como o log de transações funciona é muito limitado. Dito isto, meus superiores concluíram que não podemos usar as instruções MERGE ou UPDATE onde todas as colunas são processadas na mesma instrução, pois isso cria um log excessivo. O argumento para isso é que quando você executa uma UPDATE
-instrução no SQL Server, quando você define um valor de coluna e o novo valor é igual ao valor antigo, ele ainda é marcado como uma atualização no log de transações. Isso aparentemente se torna caro quando você executa muitas e muitas operações SET inúteis.
No exemplo a seguir, atualizamos o first_name
and last_name
da tabela de destino usando valores da tabela de origem, unidos por id
.
-- create source- and target-table
CREATE TABLE [#tgt] (
[id] Int PRIMARY KEY,
[first_name] NVarchar(10),
[last_name] NVarchar(10)
)
CREATE TABLE [#src] (
[id] Int PRIMARY KEY,
[first_name] NVarchar(10),
[last_name] NVarchar(10)
)
-- fill some dummy-data
INSERT INTO [#tgt]([id], [first_name], [last_name])VALUES(1, 'john', 'lennon')
INSERT INTO [#src]([id], [first_name], [last_name])VALUES(1, 'john', 'cena')
-- update target-table with values from source-table
UPDATE
[T]
SET
[T].[first_name] = [S].[first_name],
[T].[last_name] = [S].[last_name]
FROM
[#tgt] AS [T]
JOIN [#src] AS [S] ON [S].[id] = [T].[id]
DROP TABLE [#tgt]
DROP TABLE [#src]
Este exemplo não verifica se algum valor foi realmente alterado. Se ignorarmos NULL
-checking e fallbacks sãos por um momento, isso pode ser verificado de uma das seguintes maneiras:
-- Example #1: updates all rows where first-name or last-name has changed
UPDATE ..
SET ..
FROM ..
WHERE [T].[first_name] <> [S].[first_name] OR [T].[last_name] <> [S].[last_name]
-- Example #2: updates all rows, sets target-value to source-value if value has changed
UPDATE ..
SET
ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name]),
ISNULL(NULLIF([S].[last_name], [T].[last_name]), [T].[last_name])
FROM ..
No Exemplo #1, a operação SET atualizará todas as colunas, mesmo que apenas 1 coluna tenha sido alterada.
No Exemplo #2, a operação SET atualizará todas as colunas de todas as linhas, retornando ao valor antigo se o valor não for alterado.
Em ambos os exemplos, todas as colunas são atingidas pela operação SET e, de acordo com meus superiores, isso cria uma quantidade desnecessária/problemática de log de transações quando feito com frequência.
O mesmo se aplica para a MERGE
instrução -. Mesmo se você verificar uma linha correspondente para alterações, todas as colunas serão atingidas pela atualização.
MERGE [#tgt] AS tgt
USING [#src] AS src
ON (tgt.id = src.id)
WHEN MATCHED AND ([tgt].[first_name] <> [src].[first_name] OR [tgt].[last_name] <> [src].[last_name])
THEN UPDATE SET
[tgt].[first_name] = [src].[first_name],
[tgt].[last_name] = [src].[last_name];
Então, o que fazemos? Use uma única instrução UPDATE para cada coluna que desejamos atualizar. Nesse caso:
-- first_name
UPDATE [T]
SET [T].[first_name] = [S].[first_name]
FROM
[#tgt] AS [T]
JOIN [#src] AS [S] ON [S].[id] = [T].[id]
WHERE [T].[first_name] <> [S].[first_name]
-- last_name
UPDATE [T]
SET [T].[last_name] = [S].[last_name]
FROM
[#tgt] AS [T]
JOIN [#src] AS [S] ON [S].[id] = [T].[id]
WHERE [T].[last_name] <> [S].[last_name]
Agora, existem alguns contras nessa abordagem:
- Todas as instruções de atualização devem ser executadas na mesma transação para garantir que uma linha não seja deixada pela metade.
- É realmente uma merda escrever todo o código (imagine tabelas com mais de 50 colunas)
Parece que deve haver uma maneira mais inteligente de contornar isso, e eu apreciaria qualquer esclarecimento e correção nas declarações feitas neste post. Como mencionado anteriormente, estou apenas tentando o meu melhor para entender por que tem que ser assim.
Peço desculpas pelo post longo e obrigado desde já.
Bem, é legal da parte deles concluir isso. Mas, eles forneceram alguma evidência, ou seus scripts de teste, mostrando esse comportamento? Eu estaria interessado em ver tal teste ;-)
Este é um daqueles casos em que um pouco de conhecimento é enganoso. Sim, atualizar uma coluna para o mesmo valor exato é considerado uma atualização, assim como o teste de colunas sendo atualizadas por meio da
UPDATE()
função retornará1
enquanto a coluna estiver na instrução SET, independentemente do valor ser alterado ou não.MAS, as peças que faltam são:
Se nenhuma das colunas estiver mudando de valor, essa linha não será realmente atualizada. E se nenhuma linha for atualizada, a única atividade do log de transações serão dois registros: um
LOP_BEGIN_XACT
para marcar o início da transação e umLOP_COMMIT_XACT
para marcar o fim da transação. Mas nenhuma página de dados real ou página de índice é modificada. Isso pressupõe que "Linha(s) afetada(s)" > 0, mas nada realmente mudou.Se todas as linhas forem filtradas de forma que nenhuma linha seja atualizada (ou seja, "Linha(s) afetadas" = 0), não haverá atividade de Log de Tran.
Se qualquer uma das colunas estiver mudando de valor, as colunas adicionais que estão sendo configuradas para seu valor existente terão a mesma aparência no log de transações como não especificando as colunas que não estão mudando de valor.
Cada consulta (a menos que agrupada com outras em uma transação explícita) é sua própria transação, e cada transação no log de transações tem, no mínimo, as 2 entradas: uma para o início e outra para COMMIT ou ABORT.
Logo:
Suas duas opções de "Exemplo 1" e "Exemplo 2" são quase as mesmas no que diz respeito ao Tran Log. Se houver pelo menos uma linha para atualizar, elas devem ser as mesmas. Mas se não houver linhas em que as colunas estejam mudando de valor, o "Exemplo 1" (com a
WHERE
cláusula) resultará em menos atividade de Tran Log, pois não haverá entradas enquanto no "Exemplo 2" (todas as linhas "atualizadas") haverá as entradas BEGIN e COMMIT. Portanto, eu recomendaria usar aWHERE
cláusula, pois está sendo explícita em suas intenções e resultará em um pouco menos de atividade de Tran Log.Seguir o conselho de seus "idosos" é garantido para resultar em mais atividade de Tran Log, sem mencionar a diminuição do desempenho. Por quê? Porque:
UPDATE
instruções em uma única Transação Explícita para reduzir a inconsistência, bem como entradas de log BEGIN / END extras, você ainda estará atualizando a linha várias vezes em alguns casos, e cada modificação é registrada.UPDATE
instrução.É sempre melhor saber com certeza e ver por si mesmo, em vez de confiar em conjecturas ou no que outra pessoa afirma. Para isso, você deve testar suas várias opções, incluindo as duas atualizações separadas sugeridas por seus idosos, e após cada teste, verifique através de:
PS Eu fiz meu teste inicial no SQL Server 2012 (SP3) Developer Edition. Em seguida, testei novamente no SQL Server 2016 (RTM) Express Edition e o comportamento foi o mesmo.
PPS Logicamente,
[T].[first_name] = ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name])
não é diferente de[T].[first_name] = [S].[first_name]
, é apenas envolvido em mais funções. Mas se ambas as colunas forem'A'
, atualizar isso com um'A'
da mesma tabela em oposição a um'A'
da outra tabela é exatamente a mesma operação.PPPS Ao verificar quaisquer diferenças nos campos de string, você realmente precisa usar um Collation binário, caso contrário, pode haver alterações em maiúsculas e minúsculas (ou largura ou combinação de caracteres, etc.) . Eu percebo que você mencionou que esses foram exemplos simplificados, mas estou apenas me certificando de que esse aspecto não seja esquecido :-). Por isso:
torna-se:
E:
torna-se:
Qual é o seu nível de registro? Você sabe que o simples limpará o log de transações entre as instruções?
Se você deseja minimizar o registro, várias atualizações
Então a mesma coisa para last_name
Eu sei que parece longo mas irá minimizar o registo
As múltiplas actualizações são normalmente mais eficientes do que OR
Uh, oh, acabei de ler a mesma transação
Um ou outro vai ser muito diferente
Que tal uma vista
Basta usar a visualização durante a atualização - essa visualização deve ser muito eficiente