sql >> Base de Datos >  >> RDS >> Database

Comprobando si una columna que no es LOB necesita ser actualizada

Ocasionalmente, veo personas que intentan "optimizar" sus declaraciones de actualización para evitar escribir el mismo valor en una columna en particular. Mi entendimiento siempre ha sido que si va a actualizar una fila, suponiendo que todos los valores están en la fila, los costos de bloquear la fila son mucho más altos que el costo incremental de actualizar una, dos o todas las columnas en esa fila .

Entonces, creé una tabla simple para probar esto:

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
);

Luego creé un procedimiento almacenado para llenar la tabla con 50 000 filas con una variedad de cadenas pequeñas:

CREATE PROCEDURE dbo.clean
AS
BEGIN
  SET NOCOUNT ON;
 
  TRUNCATE TABLE dbo.whatever;
 
  ;WITH x(d) AS
  (
    SELECT d FROM
    (
      VALUES (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
   FROM x AS x1, x AS x2, x AS x3, x AS x4,
        x AS x5, x AS x6, x AS x7;
END
GO

Luego escribí declaraciones de actualización formuladas de dos maneras que podría "evitar" escribir en una columna específica, dada esta asignación de variable:

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';

Primero usando una expresión CASE para verificar si el valor en la columna es el mismo que el valor en la variable:

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 END
WHERE
(
     v1 <> @v1 OR v2 <> @v2 OR v3 <> @v3 
  OR v4 <> @v4 OR v5 <> @v5 OR v6 <> @v6
);

Y segundo, emitiendo una ACTUALIZACIÓN independiente para cada columna (cada una apuntando solo a las filas donde ese valor, de hecho, cambió):

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;

Entonces compararía esto con la forma en que la mayoría de nosotros haríamos esto hoy:simplemente ACTUALIZAR todas las columnas sin importar si ese era el valor preexistente para esa columna en particular:

UPDATE dbo.whatever SET
  v1 = @v1, v2 = @v2, v3 = @v3,
  v4 = @v4, v5 = @v5, v6 = @v6
WHERE
(
     v1 <> @v1 OR v2 <> @v2 OR v3 <> @v3 
  OR v4 <> @v4 OR v5 <> @v5 OR v6 <> @v6
);

(Todos estos asumen que las columnas y los parámetros/variables no son NULLable; necesitarían usar COALESCE para tener en cuenta la comparación de NULL en cualquier lado si ese es el caso. También asumen que tendría una cláusula WHERE adicional para filas específicas de destino:en este ejemplo, puede ejecutar la primera y la tercera consulta sin la cláusula WHERE que lo abarca todo y ver resultados casi idénticos. Mantuve esto simple por brevedad).

Luego, quería ver qué sucede en estos tres casos cuando se puede cambiar cualquier valor, cuando se pueden cambiar valores particulares, cuando no se cambia ningún valor y cuando se cambian todos los valores. Podría afectar esto cambiando el procedimiento almacenado para insertar constantes en columnas particulares, o cambiando la forma en que se asignaron las variables.

-- to show when any value might change in a row, the procedure uses the full cross join:
 
  SELECT TOP (50000) x1.d, x2.d, x3.d, x4.d, x5.d, x6.d
 
-- to show when particular values will change on many rows, we can hard-code constants:
 
  -- two values exempt:
  SELECT TOP (50000) N'abc', N'def', x3.d, x4.d, x5.d, x6.d
 
  -- four values exempt:
  SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', x5.d, x6.d
 
-- to show when no values will change, we hard-code all six values:
 
  SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', N'mno', N'pqr'
 
-- and to show when all values will change, a different variable assignment would take place:
 
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

Después de ejecutar estas pruebas, la "actualización ciega" ganó en todos los escenarios. Ahora, estás pensando, ¿qué son un par de cientos de milisegundos? Extrapolar. Si está realizando muchas actualizaciones en su sistema, esto realmente puede comenzar a pasar factura.

Resultados detallados en Plan Explorer:Cualquier cambio | 2 valores exentos | 4 valores exentos | Todos los valores exentos | Todo cambia

Según los comentarios de Roji, decidí probar esto también con algunos í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);

Las duraciones se incrementaron sustancialmente con estos índices:

Resultados detallados en Plan Explorer:Cualquier cambio | 2 valores exentos | 4 valores exentos | Todos los valores exentos | Todo cambia

Conclusión

A partir de esta prueba, me parece que generalmente no vale la pena verificar si un valor debe actualizarse. Si su declaración de ACTUALIZACIÓN afecta a varias columnas, casi siempre es más económico escanear todas las columnas donde cualquier valor podría haber cambiado en lugar de verificar cada columna individualmente. En una publicación futura, investigaré si este escenario es paralelo para las columnas LOB.