Database
 sql >> Base de Dados >  >> RDS >> Database

Verificando se uma coluna não LOB precisa ser atualizada


Ocasionalmente, vejo pessoas tentando "otimizar" suas instruções de atualização para evitar escrever o mesmo valor em uma coluna específica. Meu entendimento sempre foi que, se você for atualizar uma linha, supondo que todos os valores estejam na linha, os custos de bloqueio da linha são muito maiores do que o custo incremental de atualizar uma, duas ou todas as colunas dessa linha .

Então, criei uma tabela simples para testar isso:
CREATE TABLE dbo.whatever( ID INT IDENTITY(1,1) PRIMARY KEY, v1 NVARCHAR(50) NOT NULL, v2 NVARCHAR(50) NOT NULL, v3 NVARCHAR(50) NOT NULL, v4 NVARCHAR(50) NOT NULL, v5 NVARCHAR(50) NOT NULL, v6 NVARCHAR(50) NOT NULL);

Em seguida, criei um procedimento armazenado para preencher a tabela com 50.000 linhas com uma variedade de pequenas strings:
CRIAR PROCEDIMENTO dbo.cleanASBEGIN SET NOCOUNT ON; TRUNCATE TABLE dbo.whatever;;COM x(d) AS ( SELECT d FROM ( VALORES (N'abc'),(N'def'),(N'ghi'), (N'jkl'),(N'mno'),(N 'pqr') ) AS y(d) ) INSERT dbo.whatever(v1, v2, v3, v4, v5, v6) SELECT TOP (50000) x1.d, x2.d, x3.d, x4.d, x5 .d, x6.d DE x AS x1, x AS x2, x AS x3, x AS x4, x AS x5, x AS x6, x AS x7;ENDGO

Então eu escrevi instruções de atualização formuladas de duas maneiras que você poderia "evitar" escrever em uma coluna específica, dada esta atribuição de variável:
DECLARE @v1 NVARCHAR(50) =N'abc', @v2 NVARCHAR(50) =N'def', @v3 NVARCHAR(50) =N'ghi', @v4 NVARCHAR(50) =N'jkl ', @v5 NVARCHAR(50) =N'mno', @v6 NVARCHAR(50) =N'pqr';

Primeiro usando uma expressão CASE para verificar se o valor na coluna é o mesmo que o valor na variável:
UPDATE dbo.whatever SET v1 =CASE WHEN v1 <> @v1 ​​THEN @v1 ELSE v1 END, v2 =CASE WHEN v2 <> @v2 THEN @v2 ELSE v2 END, v3 =CASE WHEN v3 <> @v3 THEN @v3 ELSE v3 END, v4 =CASE WHEN v4 <> @v4 THEN @v4 ELSE v4 END, v5 =CASE WHEN v5 <> @v5 THEN @v5 ELSE v5 END, v6 =CASE WHEN v6 <> @v6 THEN @v6 ELSE v6 ENDWHERE( v1 <> @v1 ​​OR v2 <> @v2 OR v3 <> @v3 OR v4 <> @v4 OR v5 <> @v5 OR v6 <> @v6);

E segundo, emitindo um UPDATE independente para cada coluna (cada uma segmentando apenas as linhas em que esse valor, de fato, foi alterado):
UPDATE dbo.whatever SET v1 =@v1 WHERE v1 <> @v1;UPDATE dbo.whatever SET v2 =@v2 WHERE v2 <> @v2;UPDATE dbo.whatever SET v3 =@v3 WHERE v3 <> @v3;UPDATE dbo.whatever SET v4 =@v4 WHERE v4 <> @v4;UPDATE dbo.whatever SET v5 =@v5 WHERE v5 <> @v5;UPDATE dbo.whatever SET v6 =@v6 WHERE v6 <> @v6; 
Então eu compararia isso com a maneira como a maioria de nós faria isso hoje:apenas ATUALIZE todas as colunas sem se importar se esse era o valor pré-existente para essa coluna específica:
UPDATE dbo.whatever SET v1 =@v1, v2 =@v2, v3 =@v3, v4 =@v4, v5 =@v5, v6 =@v6WHERE( v1 <> @v1 ​​OR v2 <> @v2 OR v3 <> @v3 OU v4 <> @v4 OU v5 <> @v5 OU v6 <> @v6);

(Todos eles assumem que as colunas e os parâmetros/variáveis ​​não são NULLable – eles precisariam usar COALESCE para contabilizar a comparação de NULLs em ambos os lados, se esse for o caso. Eles também assumem que você teria uma cláusula WHERE adicional para segmentar linhas específicas – neste exemplo, você pode executar a primeira e a terceira consulta sem a cláusula WHERE abrangente e ver resultados quase idênticos. Mantive isso simples para ser breve.)

Então eu queria ver o que acontece nesses três casos quando qualquer valor pode ser alterado, quando valores específicos podem ser alterados, quando nenhum valor deve ser alterado e quando todos os valores são alterados. Eu poderia afetar isso alterando o procedimento armazenado para inserir constantes em colunas específicas ou alterando a maneira como as variáveis ​​foram atribuídas.
-- para mostrar quando qualquer valor pode ser alterado em uma linha, o procedimento usa a junção cruzada completa:SELECT TOP (50000) x1.d, x2.d, x3.d, x4.d, x5.d, x6 .d -- para mostrar quando determinados valores serão alterados em muitas linhas, podemos codificar constantes:-- dois valores isentos:SELECT TOP (50000) N'abc', N'def', x3.d, x4.d , x5.d, x6.d -- quatro valores isentos:SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', x5.d, x6.d -- para mostrar quando nenhum valor for alterado, codificamos todos os seis valores:SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', N'mno', N'pqr' -- e para mostrar quando todos os valores serão alterados, uma atribuição de variável diferente ocorreria:DECLARE @v1 NVARCHAR(50) =N'zzz', @v2 NVARCHAR(50) =N'zzz', @v3 NVARCHAR(50) =N 'zzz', @v4 NVARCHAR(50) =N'zzz', @v5 NVARCHAR(50) =N'zzz', @v6 NVARCHAR(50) =N'zzz';

Resultados


Depois de executar esses testes, a "atualização cega" venceu em todos os cenários. Agora, você está pensando, o que são algumas centenas de milissegundos? Extrapolar. Se você estiver executando muitas atualizações em seu sistema, isso pode realmente começar a prejudicar.



Resultados detalhados no Plan Explorer:Qualquer alteração | 2 valores isentos | 4 valores isentos | Todos os valores isentos | Tudo muda

Com base no feedback de Roji, decidi testar isso também com alguns índices:
CREATE INDEX x1 ON dbo.whatever(v1);CREATE INDEX x2 ON dbo.whatever(v2);CREATE INDEX x3 ON dbo.whatever(v3) INCLUDE(v4,v5,v6);

As durações foram substancialmente aumentadas com estes índices:



Resultados detalhados no Plan Explorer:Qualquer alteração | 2 valores isentos | 4 valores isentos | Todos os valores isentos | Tudo muda

Conclusão


A partir deste teste, parece-me que geralmente não vale a pena verificar se um valor deve ser atualizado. Se sua instrução UPDATE afetar várias colunas, é quase sempre mais barato verificar todas as colunas onde qualquer valor pode ter sido alterado em vez de verificar cada coluna individualmente. Em um post futuro, investigarei se esse cenário é paralelo para colunas LOB.