Las declaraciones individuales (DML, DDL, etc.) son transacciones en sí mismas. Entonces sí, después de cada iteración del ciclo (técnicamente: después de cada declaración), lo que sea que UPDATE
haya cambiado esa declaración se ha confirmado automáticamente.
Por supuesto, siempre hay una excepción, ¿verdad? Es posible habilitar transacciones implícitas a través SET IMPLICIT_TRANSACTIONS , en cuyo caso la primera UPDATE
declaración desencadenara una transacción que usted tendría que COMMIT
o ROLLBACK
al final. Esta es una configuración de nivel de sesión que está DESACTIVADA de forma predeterminada en la mayoría de los casos.
¿necesitamos agregar declaraciones explícitas de BEGIN TRANSACTION / END TRANSACTION para que podamos cancelar en cualquier momento?
No. Y de hecho, dado que desea poder detener el proceso y reiniciarlo, agregar una transacción explícita (o habilitar Transacciones implícitas) sería una mala idea, ya que detener el proceso podría atraparlo antes de que lo haga COMMIT
. En ese caso, necesitaría emitir manualmente COMMIT
(si está en SSMS), o si está ejecutando esto desde un trabajo del Agente SQL, entonces no tiene esa oportunidad y podría terminar con una transacción huérfana.
Además, es posible que desee establecer @CHUNK_SIZE
un número menor. La escalada de bloqueo generalmente ocurre en 5000 bloqueos adquiridos en un solo objeto. Dependiendo del tamaño de las filas y si está haciendo bloqueos de fila frente a bloqueos de página, es posible que esté superando ese límite. Si el tamaño de una fila es tal que solo caben 1 o 2 filas por cada página, entonces es posible que siempre esté presionando esto incluso si está bloqueando la página.
Si la tabla está particionada, tiene la opción de configurar la LOCK_ESCALATION
opción (introducida en SQL Server 2008) para que la tabla AUTO
bloquee solo la partición y no toda la tabla al escalar. O, para cualquier tabla, puede establecer esa misma opción DISABLE
, aunque tendría que tener mucho cuidado con eso. Ver ALTER TABLE para más detalles.
Aquí hay alguna documentación que habla sobre Lock Escalation y los umbrales: Lock Escalation (dice que se aplica a "SQL Server 2008 R2 y versiones superiores"). Y aquí hay una publicación de blog que trata de detectar y corregir la escalada de bloqueo: Bloqueo en Microsoft SQL Server (Parte 12 - Escalada de bloqueo) .
Sin relación con la pregunta exacta, pero relacionada con la consulta en la pregunta, hay algunas mejoras que podrían hacerse aquí (o al menos así parece al mirarla):
Para su ciclo, hacerlo WHILE (@@ROWCOUNT = @CHUNK_SIZE)
es un poco mejor ya que si el número de filas actualizadas en la última iteración es menor que la cantidad solicitada para ACTUALIZAR, entonces no queda trabajo por hacer.
Si el deleted
campo es un BIT
tipo de datos, entonces no es ese valor depende de si o no deletedDate
es 2000-01-01
? ¿Por qué necesitas ambos?
Si estos dos campos son nuevos y los agregó, por NULL
lo que podría ser una operación en línea / sin bloqueo y ahora desea actualizarlos a su valor "predeterminado", entonces eso no fue necesario. A partir de SQL Server 2012 (solo Enterprise Edition), agregar NOT NULL
columnas que tengan una restricción DEFAULT son operaciones sin bloqueo siempre que el valor de DEFAULT sea una constante. Entonces, si aún no está utilizando los campos, simplemente suelte y vuelva a agregar como NOT NULL
y con una restricción DEFAULT.
Si ningún otro proceso está actualizando estos campos mientras está haciendo esta ACTUALIZACIÓN, entonces sería más rápido si pusiera en cola los registros que deseaba actualizar y luego simplemente saliera de esa cola. Hay un impacto en el rendimiento en el método actual, ya que debe volver a consultar la tabla cada vez para obtener el conjunto que debe cambiarse. En su lugar, puede hacer lo siguiente, que solo escanea la tabla una vez en esos dos campos y luego emite solo declaraciones de ACTUALIZACIÓN muy específicas. Tampoco hay penalización por detener el proceso en cualquier momento e iniciarlo más tarde, ya que la población inicial de la cola simplemente encontrará los registros que quedan para actualizar.
- Cree una tabla temporal (#FullSet) que solo contenga los campos clave del índice agrupado.
- Cree una segunda tabla temporal (#CurrentSet) de esa misma estructura.
insertar en #FullSet a través de SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;
El TOP(n)
está allí debido al tamaño de la mesa. Con 100 millones de filas en la tabla, realmente no necesita llenar la tabla de la cola con todo ese conjunto de claves, especialmente si planea detener el proceso de vez en cuando y reiniciarlo más tarde. Por lo tanto, puede establecer n
en 1 millón y dejar que se ejecute hasta su finalización. Siempre puede programar esto en un trabajo del Agente SQL que ejecuta el conjunto de 1 millón (o tal vez incluso menos) y luego espera la próxima hora programada para retomar el trabajo. Luego, puede programar que se ejecute cada 20 minutos para que haya un respiro forzado entre series de n
, pero aún así terminará todo el proceso sin supervisión. Luego, haga que el trabajo se elimine solo cuando no haya nada más que hacer :-).
- en un bucle, hacer:
- Rellene el lote actual a través de algo como
DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
IF (@@ROWCOUNT = 0) BREAK;
- Haga la ACTUALIZACIÓN usando algo como:
UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
- Borrar el conjunto actual:
TRUNCATE TABLE #CurrentSet;
- En algunos casos, ayuda agregar un índice filtrado para ayudar a
SELECT
que se alimente a la #FullSet
tabla temporal. Aquí hay algunas consideraciones relacionadas con la adición de dicho índice:
- La condición WHERE debe coincidir con la condición WHERE de su consulta, por lo tanto
WHERE deleted is null or deletedDate is null
- Al comienzo del proceso, la mayoría de las filas coincidirán con su condición WHERE, por lo que un índice no es tan útil. Es posible que desee esperar hasta alrededor del 50% antes de agregar esto. Por supuesto, cuánto ayuda y cuándo es mejor agregar el índice varía debido a varios factores, por lo que es un poco de prueba y error.
- Es posible que deba ACTUALIZAR ESTADÍSTICAS manualmente y / o RECONSTRUIR el índice para mantenerlo actualizado ya que los datos base cambian con bastante frecuencia
- Asegúrese de tener en cuenta que el índice, mientras ayuda
SELECT
, dañará el UPDATE
ya que es otro objeto que debe actualizarse durante esa operación, por lo tanto, más E / S. Esto se aplica tanto al uso de un índice filtrado (que se reduce a medida que actualiza las filas, ya que hay menos filas que coinciden con el filtro), y a esperar un poco para agregar el índice (si no va a ser muy útil al principio, entonces no hay razón para incurrir La E / S adicional).
ACTUALIZACIÓN: consulte mi respuesta a una pregunta relacionada con esta pregunta para la implementación completa de lo que se sugiere anteriormente, incluido un mecanismo para rastrear el estado y cancelar de forma limpia: servidor SQL: actualización de campos en una tabla enorme en pequeños fragmentos: cómo obtener ¿status del progreso?