8

tengo un procedimiento almacenado que realiza una combinación de TableB-TableA:SQL Server Deadlock Fix: ¿Forzar orden de unión o volver a intentarlo automáticamente?

SELECT <--- Nested <--- TableA 
      Loop <-- 
         | 
         ---TableB 

Al mismo tiempo, en una transacción, las filas se insertan en TableA, y luego en TableB.

Esta situación es a veces causando bloqueos, ya que el procedimiento almacenado seleccione agarra filas de TableB, mientras que la inserción añade filas a TableA, y luego cada uno quiere que el otro para dejar de lado la otra tabla:

INSERT  SELECT 
========= ======== 
Lock A  Lock B 
Insert A Select B 
Want B  Want A 
....deadlock... 

lógica requiere la INSERT añadir filas primera a Un, y luego a B, mientras que personalmente no me importa el orden en que SQL Server realiza su unen - siempre y cuando se une.

La recomendación común para la fijación de interbloqueos es garantizar que todos accedan a los recursos en el mismo orden. Pero en este caso, el optimizador de SQL Server me dice que el orden opuesto es "mejor". Puedo forzar otra orden de unión y tener una consulta de peor rendimiento.

¿Pero debería?

¿Debo anular el optimizador, ahora y para siempre, con un orden de combinación que quiero que use?

¿O debería simplemente atrapar error error nativo 1205 y reenviar la instrucción select?

La pregunta no es cuánto empeoraría la consulta al anular el optimizador y hacer algo no óptimo. La pregunta es: ¿es mejor volver a intentar automáticamente, en lugar de ejecutar peores consultas?

Respuesta

9

Es mejor volver a intentar automáticamente los interbloqueos. El motivo es que puede corregir este punto muerto, solo para presionar otro más adelante. El comportamiento puede cambiar entre versiones de SQL, si el tamaño de las tablas cambia, si cambian las especificaciones de hardware del servidor e incluso si cambia la carga en el servidor. Si el interbloqueo es frecuente, debe tomar medidas activas para eliminarlo (un índice suele ser la respuesta), pero para bloqueos raros (por ejemplo, cada 10 minutos aproximadamente), intente de nuevo en la aplicación puede enmascarar el interbloqueo. Puede volver a intentar las lecturas o, ya que las escrituras, por supuesto, están rodeadas por una transacción de inicio/transacción de confirmación adecuada para mantener atómicas todas las operaciones de escritura y, por lo tanto, poder volver a intentarlas sin problemas.

Otra vía a tener en cuenta es encender read committed snapshot. Cuando esto está habilitado, SELECT simplemente no tomará bloqueos, pero producirá lecturas consistentes.

+0

Remus, retrying lee tiene perfecto sentido, pero reintentar escrituras automáticamente después de bloqueos lleva a la pérdida de actualizaciones. Cuando escribe y se convierte en una víctima de punto muerto, es probable que los datos que va a tocar hayan sido modificados por otra persona. No deberíamos reescribir automáticamente en tales casos. Deberíamos volver a leer los datos posiblemente modificados, y considerar nuevamente si queremos guardar. ¿Tiene sentido? –

+1

@AlexKuznetsov: Eso es absurdo, si una transacción se escribe correctamente (es decir, atómicamente), entonces, ¿cómo podría reintentarse si resulta en una actualización perdida? Le doy este +1, definitivamente es la respuesta correcta. No puede detener cada punto muerto, es solo parte del ruido de fondo con semántica ÁCIDA. – Aaronaught

+0

@Alex, @ Aaro: En realidad ambos tienen razón. Por "reintentar" me refiero a "leer el estado actual, aplicar cambios, escribir nuevo estado". Para aplicaciones de procesamiento automatizado, este es un patrón muy fácil de lograr. Sin embargo, para las aplicaciones interactivas del usuario, esto puede ser más difícil y, a menudo, la acción adecuada es retrasar la "escritura" al volver a leer el estado actual y volver a mostrarlo al usuario, para que pueda confirmar que los cambios aplicados tiene sentido en el nuevo estado/contexto, y creo que esto es lo que Alex tenía en mente. Por lo tanto, la acción correcta depende de cada caso. –

2

Reventar y volver a ejecutar puede funcionar, pero ¿está seguro de que SELECT siempre es la víctima del interbloqueo? Si el inserto es la víctima del interbloqueo, tendrá que ser mucho más cuidadoso al volver a intentarlo.

La solución más fácil en este caso, creo, es NOLOCK o READUNCOMMITTED (lo mismo) su selección. Las personas tienen preocupaciones justificables sobre lecturas sucias, pero hemos ejecutado NOLOCK en todas partes para una mayor concurrencia durante años y nunca hemos tenido un problema.

También me gustaría investigar un poco más acerca de la semántica de bloqueo. Por ejemplo, creo que si establece el nivel de aislamiento de la transacción en instantánea (requiere 2005 o posterior), sus problemas desaparecerán.

+0

SQL Server revierte la transacción con la menor cantidad de recursos retenidos. El "inserto" es una serie transaccionada de tal vez una docena de insertos. La selección es una selección en solitario (envuelta en un procedimiento almacenado) –

+0

@Ian Boyd: una sola instrucción 'SELECT' no puede crear una situación de interbloqueo. Necesita tener al menos dos transacciones de extractos múltiples. No es necesario que ambos sean DML, pero ambos deben esperar bloqueos en los recursos de los demás y eso significa que ambos deben usar al menos dos recursos. Si en realidad es solo una declaración 'SELECT', no envuelta en una transacción más grande, entonces puede que no sea un punto muerto real, puede ser que el sistema de E/S tenga problemas para mantenerse al día o algún otro problema extraño con el servidor. – Aaronaught

+0

@Aaronaught. Una sola selección ** puede ** causar un punto muerto con otro proceso (http://blogs.msdn.com/bartd/archive/2006/09/25/770928.aspx) –

5

Para evitar los puntos muertos, una de las recomendaciones más comunes es "para adquirir bloqueos en el mismo orden" u "objetos de acceso en el mismo orden". Claramente, esto tiene mucho sentido, pero ¿siempre es factible? ¿Es siempre posible? Sigo teniendo casos cuando no puedo seguir este consejo.

Si puedo almacenar un objeto en una tabla primaria y uno o más los niños, no pueden seguir este consejo en absoluto. Al insertar, necesito insertar mi fila principal primero. Al eliminar, tengo que hacerlo en el orden opuesto.

Si utilizo comandos que tocan varias tablas o varias filas en una tabla, generalmente no tengo control sobre los bloqueos de órdenes que se adquieren (suponiendo que no estoy usando pistas).

Por lo tanto, en muchos casos, tratando de adquirir bloqueos en el mismo orden no impide que todos los puntos muertos. Entonces, necesitamos algún tipo de bloqueo de manejo: no podemos asumir que podemos eliminarlos a todos. A menos que, por supuesto, serialicemos todos los accesos usando Service Broker o sp_getapplock.

Cuando intentarlo después de callejones sin salida, que son muy propensos a sobrescribir los cambios de otros procesos. Debemos ser conscientes de que muy probablemente alguien modificó los datos que intentamos modificar. Especialmente si todos los lectores se ejecutan bajo aislamiento instantáneo, entonces los lectores no pueden estar involucrados en interbloqueos, lo que significa que todas las partes involucradas en un punto muerto son escritores, modificados o intentaron modificar los mismos datos. Si detectamos la excepción y volvemos a intentar automáticamente, podemos sobrescribir los cambios de otra persona.

esto se le llama pérdida de actualizaciones, y esto es por lo general mal. Normalmente, lo que hay que hacer después de un punto muerto es volver a intentar en un nivel mucho más alto: volver a seleccionar los datos y decidir si se guarda de la misma manera en que se tomó la decisión original de guardar.

Por ejemplo, si un usuario presiona un botón Guardar y se elige la transacción de salvar como víctima de interbloqueo, podría ser una buena idea volver a visualizar los datos en la pantalla después del punto muerto.

+0

+1 esto es cierto en aplicaciones interactivas: si una escritura se ha bloqueado, es muy probable que el estado que se estaba * actualizando * haya * cambiado, ya que ese es exactamente el recurso en el que se produjo el interbloqueo. Mi respuesta estuvo influenciada por mi experiencia en el procesamiento de colas, donde el 'nivel superior' está contenido en la transacción que retrocede. –

+0

@AlexKuznetsov: no estoy tan de acuerdo con el peligro de volver a intentar una actualización. Si el usuario hace clic en el botón 200 ms más tarde, en lugar de antes, el efecto habría sido el mismo. –

+0

Si su aplicación ya está diseñada para admitir la simultaneidad optimista, entonces tiene sentido tratar un punto muerto como un conflicto. Si la aplicación habría sobrescrito los cambios de todos modos, entonces también podría volver a intentar la actualización. – Aaronaught

Cuestiones relacionadas