Si tengo una declaración de ACTUALIZACIÓN que en realidad no cambia ningún dato (porque los datos ya están en el estado actualizado), ¿hay algún beneficio de rendimiento al marcar la cláusula where para evitar la actualización?
Ciertamente, podría haber una ligera diferencia de rendimiento debido a la ACTUALIZACIÓN 1 :
- en realidad no actualiza ninguna fila (por lo tanto, no hay nada que escribir en el disco, ni siquiera una mínima actividad de registro), y
- sacar bloqueos menos restrictivos que los necesarios para realizar la actualización real (por lo tanto, mejor para la concurrencia) ( consulte la sección Actualización hacia el final )
Sin embargo, la diferencia que hay que medir en su sistema con su esquema, datos y carga del sistema. Hay varios factores que influyen en el impacto que tiene una ACTUALIZACIÓN que no se actualiza:
- la cantidad de contención sobre la tabla que se actualiza
- el número de filas que se actualizan
- si hay ACTUALIZADOR Disparadores en la tabla que se está actualizando (como señaló Mark en un comentario sobre la Pregunta). Si ejecuta
UPDATE TableName SET Field1 = Field1
, se activará un desencadenador de actualización e indicará que el campo se actualizó (si marca usando las funciones UPDATE () o COLUMNS_UPDATED ), y que el campo en ambas tablas INSERTED
y en las DELETED
tablas tiene el mismo valor.
Además, la siguiente sección de resumen se encuentra en el artículo de Paul White, El impacto de las actualizaciones no actualizadas (como lo señaló @spaghettidba en un comentario sobre su respuesta):
SQL Server contiene una serie de optimizaciones para evitar el registro innecesario o el vaciado de páginas al procesar una operación de ACTUALIZACIÓN que no dará como resultado ningún cambio en la base de datos persistente.
- Las actualizaciones que no se actualizan en una tabla en clúster generalmente evitan el registro adicional y el vaciado de páginas, a menos que la operación de actualización afecte a una columna que forma (parte de) la clave del clúster.
- Si alguna parte de la clave del clúster se 'actualiza' al mismo valor, la operación se registra como si los datos hubieran cambiado y las páginas afectadas se marcan como sucias en el grupo de búferes. Esto es una consecuencia de la conversión de la ACTUALIZACIÓN a una operación de eliminar y luego insertar.
- Las tablas de montón se comportan de la misma manera que las tablas en clúster, excepto que no tienen una clave de clúster que provoque un registro adicional o un vaciado de página. Este sigue siendo el caso incluso cuando existe una clave primaria no agrupada en el montón. Por lo tanto, las actualizaciones que no se actualizan en un montón generalmente evitan el registro y el vaciado adicionales (pero vea a continuación).
- Tanto los montones como las tablas en clúster sufrirán el registro y el vaciado adicionales para cualquier fila donde una columna LOB que contiene más de 8000 bytes de datos se actualice al mismo valor usando cualquier sintaxis que no sea 'SET column_name = column_name'.
- Simplemente habilitar cualquier tipo de nivel de aislamiento de versiones de fila en una base de datos siempre provoca el registro y el vaciado adicionales. Esto ocurre independientemente del nivel de aislamiento vigente para la transacción de actualización.
Tenga en cuenta (especialmente si no sigue el enlace para ver el artículo completo de Paul), los siguientes dos elementos:
Las actualizaciones que no se actualizan todavía tienen alguna actividad de registro, lo que muestra que una transacción está comenzando y terminando. Es solo que no ocurre ninguna modificación de datos (lo que sigue siendo un buen ahorro).
Como dije anteriormente, debe probar en su sistema. Use las mismas consultas de investigación que Paul está usando y vea si obtiene los mismos resultados. Estoy viendo resultados ligeramente diferentes en mi sistema de lo que se muestra en el artículo. Todavía no hay páginas sucias para escribir, pero un poco más de actividad de registro.
... Necesito que el recuento de filas incluya la fila sin cambios, así que sé si hacer una inserción si la ID no existe. ... ¿es posible obtener el recuento de filas que necesito de alguna manera?
Simplísticamente, si solo está tratando con una sola fila, puede hacer lo siguiente:
UPDATE MyTable
SET Value = 2
WHERE ID = 2
AND Value <> 2;
IF (@@ROWCOUNT = 0)
BEGIN
IF (NOT EXISTS(
SELECT *
FROM MyTable
WHERE ID = 2 -- or Value = 2 depending on the scenario
)
)
BEGIN
INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
VALUES (2, 2);
END;
END;
Para varias filas, puede obtener la información necesaria para tomar esa decisión utilizando la OUTPUT
cláusula Al capturar exactamente qué filas se actualizaron, puede reducir los elementos para buscar y conocer la diferencia entre no actualizar filas que no existen en lugar de no actualizar filas que existen pero que no necesitan la actualización.
Muestro la implementación básica en la siguiente respuesta:
¿Cómo evitar el uso de la consulta de combinación al insertar múltiples datos usando el parámetro xml?
El método que se muestra en esa respuesta no filtra las filas que existen, pero no es necesario actualizarlas. Esa parte podría agregarse, pero primero debería mostrar exactamente dónde está obteniendo su conjunto de datos en el que se está fusionando MyTable
. ¿Vienen de una mesa temporal? ¿Un parámetro con valores de tabla (TVP)?
ACTUALIZACIÓN 1:
Finalmente pude hacer algunas pruebas y esto es lo que encontré con respecto al registro de transacciones y el bloqueo. Primero, el esquema de la tabla:
CREATE TABLE [dbo].[Test]
(
[ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
[StringField] [varchar](500) NULL
);
A continuación, la prueba actualiza el campo al valor que ya tiene:
UPDATE rt
SET rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM dbo.Test rt
WHERE rt.ID = 4082117
Resultados:
-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT
-- SQL Profiler (3 Lock:Acquired events):
Mode Type
--------------------------------------
8 - IX 5 - OBJECT
8 - IX 6 - PAGE
5 - X 7 - KEY
Finalmente, la prueba que filtra la actualización debido a que el valor no cambia:
UPDATE rt
SET rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM dbo.Test rt
WHERE rt.ID = 4082117
AND rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';
Resultados:
-- Transaction Log (0 entries):
Operation
----------------------------
-- SQL Profiler (3 Lock:Acquired events):
Mode Type
--------------------------------------
8 - IX 5 - OBJECT
7 - IU 6 - PAGE
4 - U 7 - KEY
Como puede ver, no se escribe nada en el Registro de transacciones al filtrar la fila, a diferencia de las dos entradas que marcan el comienzo y el final de la Transacción. Y si bien es cierto que esas dos entradas son casi nada, siguen siendo algo.
Además, el bloqueo de los recursos PAGE y KEY es menos restrictivo cuando se filtran las filas que no han cambiado. Si ningún otro proceso está interactuando con esta tabla, entonces es probable que no sea un problema (pero ¿qué tan probable es eso realmente?). Tenga en cuenta que las pruebas que se muestran en cualquiera de los blogs vinculados (e incluso mis pruebas) suponen implícitamente que no hay contención en la tabla ya que nunca forma parte de las pruebas. Al decir que las actualizaciones que no se actualizan son tan livianas que no vale la pena realizar el filtrado, deben tomarse con un grano de sal ya que las pruebas se han realizado, más o menos, en el vacío. Pero en Producción, esta tabla probablemente no esté aislada. Por supuesto, bien podría ser que el poco registro y los bloqueos más restrictivos no se traduzcan en menos eficiencia. Entonces, ¿la fuente de información más confiable para responder esta pregunta? Servidor SQL. Específicamente:su servidor SQL Le mostrará qué método es mejor para su sistema :-).
ACTUALIZACIÓN 2:
Si las operaciones en las que el nuevo valor es el mismo que el valor actual (es decir, sin actualización), enumere las operaciones en las que el nuevo valor es diferente y la actualización es necesaria, entonces el siguiente patrón podría ser aún mejor, especialmente si Hay mucha discusión sobre la mesa. La idea es hacer un simple SELECT
primero para obtener el valor actual. Si no obtiene un valor, entonces tiene su respuesta con respecto al INSERT
. Si tiene un valor, puede hacer un simple IF
y emitir el UPDATE
único si es necesario.
DECLARE @CurrentValue VARCHAR(500) = NULL,
@NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
@ID INT = 4082117;
SELECT @CurrentValue = rt.StringField
FROM dbo.Test rt
WHERE rt.ID = @ID;
IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
-- row does not exist
INSERT INTO dbo.Test (ID, StringField)
VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
-- row exists, so check value to see if it is different
IF (@CurrentValue <> @NewValue)
BEGIN
-- value is different, so do the update
UPDATE rt
SET rt.StringField = @NewValue
FROM dbo.Test rt
WHERE rt.ID = @ID;
END;
END;
Resultados:
-- Transaction Log (0 entries):
Operation
----------------------------
-- SQL Profiler (2 Lock:Acquired events):
Mode Type
--------------------------------------
6 - IS 5 - OBJECT
6 - IS 6 - PAGE
Por lo tanto, solo hay 2 bloqueos adquiridos en lugar de 3, y ambos bloqueos son Intent Shared, no Intent eXclusive o Intent Update ( Compatibilidad de bloqueo ). Teniendo en cuenta que cada bloqueo adquirido también se liberará, cada bloqueo es realmente 2 operaciones, por lo que este nuevo método es un total de 4 operaciones en lugar de las 6 operaciones en el método propuesto originalmente. Teniendo en cuenta que esta operación se ejecuta una vez cada 15 ms (aproximadamente, como lo indica el OP), eso es aproximadamente 66 veces por segundo. Entonces, la propuesta original asciende a 396 operaciones de bloqueo / desbloqueo por segundo, mientras que este nuevo método equivale a solo 264 operaciones de bloqueo / desbloqueo por segundo de bloqueos aún más livianos. Esto no es garantía de un rendimiento increíble, pero ciertamente vale la pena probarlo :-).