Estamos intentando actualizar / eliminar una gran cantidad de registros en una tabla de filas multimillonaria. Como esta es una tabla popular, hay mucha actividad en diferentes secciones de esta tabla. Cualquier actividad grande de actualización / eliminación se bloquea durante largos períodos de tiempo (ya que está esperando obtener bloqueos en todas las filas o bloqueo de página o bloqueo de tabla), lo que resulta en tiempos de espera o toma varios días para completar la tarea.
Por lo tanto, estamos cambiando el enfoque para eliminar pequeños lotes de filas a la vez. Pero queremos verificar si los seleccionados (digamos 100 o 1000 o 2000 filas) están bloqueados actualmente por un proceso diferente o no.
- De lo contrario, proceda con la eliminación / actualización.
- Si están bloqueados, pase al siguiente grupo de registros.
- Al final, regrese al principio e intente actualizar / eliminar los que quedan fuera.
¿Es esto factible?
Gracias, ToC
Respuestas:
Si entiendo la solicitud correctamente, el objetivo es eliminar lotes de filas, mientras que, al mismo tiempo, se producen operaciones DML en filas en toda la tabla. El objetivo es eliminar un lote; sin embargo, si cualquier fila subyacente contenida dentro del rango definido por dicho lote está bloqueada, entonces debemos omitir ese lote y pasar al siguiente lote. Luego debemos volver a los lotes que no se eliminaron previamente y volver a intentar nuestra lógica de eliminación original. Debemos repetir este ciclo hasta que se eliminen todos los lotes de filas requeridos.
Como se ha mencionado, es razonable usar una sugerencia READPAST y el nivel de aislamiento READ COMMITTED (predeterminado), para omitir los rangos pasados que pueden contener filas bloqueadas. Iré un paso más allá y recomendaré usar el nivel de aislamiento SERIALIZABLE y eliminar eliminaciones.
SQL Server usa bloqueos de rango clave para proteger un rango de filas implícitamente incluido en un conjunto de registros que se lee mediante una instrucción Transact-SQL mientras se usa el nivel de aislamiento de transacción serializable ... encuentre más aquí: https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
Con las eliminaciones de mordiscos, nuestro objetivo es aislar un rango de filas y asegurarnos de que no se produzcan cambios en esas filas mientras las eliminamos, es decir, no queremos lecturas o inserciones fantasmas. El nivel de aislamiento serializable está destinado a resolver este problema.
Antes de demostrar mi solución, me gustaría agregar que tampoco recomiendo cambiar el nivel de aislamiento predeterminado de su base de datos a SERIALIZABLE ni recomiendo que mi solución sea la mejor. Simplemente deseo presentarlo y ver a dónde podemos ir desde aquí.
Algunas notas de mantenimiento:
Para comenzar mi experimento, configuraré una base de datos de prueba, una tabla de muestra y llenaré la tabla con 2,000,000 filas.
En este punto, necesitaremos uno o más índices sobre los que puedan actuar los mecanismos de bloqueo del nivel de aislamiento SERIALIZABLE.
Ahora, verifiquemos que se crearon nuestras 2,000,000 filas
Entonces, tenemos nuestra base de datos, tabla, índices y filas. Entonces, configuremos el experimento para eliminar eliminaciones. Primero, debemos decidir cuál es la mejor manera de crear un mecanismo típico de eliminación de mordiscos.
Como puede ver, coloqué la transacción explícita dentro del ciclo while. Si desea limitar las descargas de registros, no dude en colocarlo fuera del ciclo. Además, dado que estamos en el modelo de recuperación COMPLETA, es posible que desee crear copias de seguridad del registro de transacciones con mayor frecuencia mientras ejecuta sus operaciones de eliminación de mordiscos, para garantizar que su registro de transacciones no pueda crecer escandalosamente.
Entonces, tengo un par de objetivos con esta configuración. Primero, quiero mis cerraduras de rango de llave; entonces, trato de mantener los lotes lo más pequeños posible. Tampoco quiero impactar negativamente la concurrencia en mi tabla "gigantesca"; Entonces, quiero tomar mis cerraduras y dejarlas tan rápido como pueda. Por lo tanto, le recomiendo que reduzca el tamaño de sus lotes.
Ahora, quiero proporcionar un ejemplo muy breve de esta rutina de eliminación en acción. Debemos abrir una nueva ventana dentro de SSMS y eliminar una fila de nuestra tabla. Haré esto dentro de una transacción implícita usando el nivel de aislamiento predeterminado LEER COMPROMETIDO.
¿Esta fila se eliminó realmente?
Sí, fue eliminado.
Ahora, para ver nuestros bloqueos, abramos una nueva ventana dentro de SSMS y agreguemos un fragmento de código o dos. Estoy usando el sp_whoisactive de Adam Mechanic, que se puede encontrar aquí: sp_whoisactive
Ahora estamos listos para comenzar. En una nueva ventana de SSMS, comencemos una transacción explícita que intentará volver a insertar la fila que eliminamos. Al mismo tiempo, activaremos nuestra operación de eliminación de mordiscos.
El código de inserción:
Comencemos ambas operaciones comenzando con la inserción y seguidas por nuestras eliminaciones. Podemos ver las cerraduras de la gama de llaves y las cerraduras exclusivas.
El inserto generó estos bloqueos:
La eliminación / selección mordisqueada mantiene estos bloqueos:
Nuestra inserción está bloqueando nuestra eliminación como se esperaba:
Ahora, confirmemos la transacción de inserción y veamos qué sucede.
Y como se esperaba, todas las transacciones se completan. Ahora, debemos verificar si la inserción fue fantasma o si la operación de eliminación también la eliminó.
De hecho, el inserto fue eliminado; entonces, no se permitió ningún inserto fantasma.
Entonces, en conclusión, creo que la verdadera intención de este ejercicio no es tratar de rastrear cada fila, página o bloqueo a nivel de tabla e intentar determinar si un elemento de un lote está bloqueado y, por lo tanto, requeriría nuestra operación de eliminación para Espere. Esa pudo haber sido la intención de los interrogadores; sin embargo, esa tarea es hercúlea y básicamente poco práctica, si no imposible. El objetivo real es garantizar que no surjan fenómenos no deseados una vez que hayamos aislado el rango de nuestro lote con bloqueos propios y luego precedamos a eliminar el lote. El nivel de aislamiento SERIALIZABLE logra este objetivo. La clave es mantener sus bocaditos pequeños, su registro de transacciones bajo control y eliminar los fenómenos no deseados.
Si desea velocidad, no cree tablas gigantescas que no puedan dividirse y, por lo tanto, no pueda utilizar el cambio de partición para obtener los resultados más rápidos. La clave para acelerar es el particionamiento y el paralelismo; la clave del sufrimiento son los mordiscos y el bloqueo en vivo.
Por favor déjame saber lo que piensa.
Creé algunos ejemplos adicionales del nivel de aislamiento SERIALIZABLE en acción. Deben estar disponibles en los enlaces a continuación.
Eliminar operación
Insertar operación
Operaciones de igualdad: bloqueos de rango clave en los siguientes valores clave
Operaciones de igualdad: recuperación de datos existentes de Singleton
Operaciones de igualdad: recuperación de datos no existentes de Singleton
Operaciones de desigualdad: bloqueos de rango clave en rango y valores clave siguientes
fuente
Esta es una muy buena idea para eliminar en pequeños lotes o fragmentos cuidadosos . Me gustaría añadir una pequeña
waitfor delay '00:00:05'
y dependiendo del modelo de recuperación de la base de datos - SiFULL
, a continuación, hacer unalog backup
y siSIMPLE
luego hacer unamanual CHECKPOINT
para evitar la hinchazón de registro de transacciones - entre lotes.Lo que está diciendo no es totalmente posible fuera de la caja (teniendo en cuenta sus 3 puntos). Si la sugerencia anterior
small batches + waitfor delay
no funciona (siempre que realice las pruebas adecuadas), puede usar elquery HINT
.No lo use
NOLOCK
- vea kb / 308886 , Problemas de coherencia de lectura del servidor SQL por Itzik Ben-Gan , poniendo NOLOCK en todas partes - Por Aaron Bertrand y SQL Server NOLOCK Hint y otras ideas pobres .READPAST
Sugerencia ayudará en su escenario. La esencia de laREADPAST
pista es: si hay un bloqueo de nivel de fila, el servidor SQL no lo leerá.Durante mis pruebas limitadas, encontré un rendimiento realmente bueno cuando utilicé
DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)
y configuré el nivel de aislamiento de la sesión de consulta paraREAD COMMITTED
usar,SET TRANSACTION ISOLATION LEVEL READ COMMITTED
que de todos modos es el nivel de aislamiento predeterminado.fuente
Resumiendo otros enfoques originalmente ofrecidos en los comentarios a la pregunta.
Úselo
NOWAIT
si el comportamiento deseado es fallar todo el fragmento tan pronto como se encuentre un bloqueo incompatible.De la
NOWAIT
documentación :Úselo
SET LOCK_TIMEOUT
para lograr un resultado similar, pero con un tiempo de espera configurable:De la
SET LOCK_TIMEOUT
documentaciónfuente
Para suponer que tenemos 2 consultas paralelas:
conectar / sesión 1: bloqueará la fila = 777
conectar / sesión 2: ignorará la fila bloqueada = 777
O conecte / sesión 2: lanzará una excepción
fuente
Intenta filtrar algo como esto: puede ser complicado si quieres ser muy, muy específico. Busque en BOL la descripción de sys.dm_tran_locks
fuente
Puede usar NoLOCK mientras está eliminando y si las filas están bloqueadas, no se eliminarán. No es ideal, pero puede hacer el truco para usted.
fuente
Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements.
, en desuso desde 2005