Recientemente, una de nuestras aplicaciones ASP.NET mostró un error de bloqueo de la base de datos y se me solicitó que verificara y corrigiera el error. Logré encontrar que la causa del punto muerto era un procedimiento almacenado que actualizaba rigurosamente una tabla dentro de un cursor.
Esta es la primera vez que veo este error y no sabía cómo rastrearlo y solucionarlo de manera efectiva. ¡Intenté todas las formas posibles que conozco y finalmente descubrí que la tabla que se está actualizando no tiene una clave principal! Afortunadamente era una columna de identidad.
Más tarde encontré al desarrollador que creó una base de datos con script para la implementación en mal estado. Agregué una clave principal y el problema se resolvió.
Me sentí feliz y volví a mi proyecto, e investigué un poco para descubrir la razón de ese punto muerto ...
Aparentemente, fue una condición de espera circular que causó el punto muerto. Las actualizaciones aparentemente tardan más sin una clave primaria que con la clave primaria.
Sé que no es una conclusión bien definida, es por eso que estoy publicando aquí ...
- ¿La clave primaria que falta es el problema?
- ¿Existen otras condiciones que causen un punto muerto que no sea (exclusión mutua, retención y espera, sin preferencia y espera circular)?
- ¿Cómo puedo prevenir y rastrear puntos muertos?
fuente
Respuestas:
rastrear puntos muertos es el más fácil de los dos:
La prevención es más difícil, esencialmente debes tener en cuenta lo siguiente:
El bloque de código 1 bloquea el recurso A, luego el recurso B, en ese orden.
El bloque de código 2 bloquea el recurso B, luego el recurso A, en ese orden.
Esta es la condición clásica en la que puede ocurrir un punto muerto, si el bloqueo de ambos recursos no es atómico, el Bloque de código 1 puede bloquear a A y ser adelantado, luego el Bloque de código 2 bloquea B antes de que A recupere el tiempo de procesamiento. Ahora tienes un punto muerto.
Para prevenir esta condición, puede hacer algo como lo siguiente
Bloque de código A (código psuedo)
Bloque de código B (pseudocódigo)
sin olvidar desbloquear A y B cuando haya terminado con ellos
esto evitaría el bloqueo entre el bloque de código A y el bloque de código B
Desde la perspectiva de la base de datos, no estoy seguro de cómo evitar esta situación, ya que la base de datos maneja los bloqueos, es decir, los bloqueos de fila / tabla al actualizar los datos. Donde he visto que ocurren más problemas es donde viste el tuyo, dentro de un cursor. Los cursores son notoriamente ineficientes, evítelos si es posible.
fuente
mis artículos favoritos para leer y aprender sobre puntos muertos son: Charla simple : localizar puntos muertos y SQL Server Central: usar Profiler para resolver puntos muertos . Le darán muestras y consejos sobre cómo manejar una situación difícil.
En resumen, para resolver un problema actual, acortaría las transacciones involucradas, sacaría la parte innecesaria de ellas, me ocuparía del orden de uso de los objetos, vería qué nivel de aislamiento es realmente necesario, no leeré innecesariamente datos...
Pero mejor lea los artículos, serán mucho más amables en los consejos.
fuente
A veces, un punto muerto se puede resolver agregando indexación, ya que permite que la base de datos bloquee registros individuales en lugar de toda la tabla, por lo que reduce la contención y la posibilidad de que las cosas se atasquen.
Por ejemplo, en InnoDB :
Otra solución común es desactivar la coherencia transaccional cuando no es necesario, o cambiar su nivel de aislamiento , por ejemplo, un trabajo de larga duración para calcular estadísticas ... una respuesta cercana generalmente es suficiente, no necesita números precisos, a medida que cambian debajo de ti. Y si tarda 30 minutos en completarse, no desea que detenga todas las demás transacciones en esas tablas.
...
En cuanto a su seguimiento, depende del software de base de datos que esté utilizando.
fuente
Solo para desarrollar en el cursor. de hecho es realmente malo. Bloquea toda la tabla y luego procesa las filas una por una.
Es mejor recorrer las filas a la manera de un cursor usando un ciclo while
En el ciclo while, se realizará una selección para cada fila del ciclo y el bloqueo se producirá en una sola fila a la vez. El resto de los datos en la tabla es gratuito para consultas, lo que reduce las posibilidades de que ocurra un punto muerto.
Además es más rápido. Hace que te preguntes por qué hay cursores de todos modos.
Aquí hay un ejemplo de este tipo de estructura:
Si su campo de ID es escaso, es posible que desee obtener una lista separada de ID e iterar a través de eso:
fuente
Falta una clave principal no es el problema. Al menos por sí mismo. Primero, no necesita un primario para tener índices. En segundo lugar, incluso si está haciendo escaneos de tabla (lo que tiene que suceder si su consulta particular no está utilizando un índice, un bloqueo de tabla no provocará un punto muerto por sí solo. Un proceso de escritura esperaría una lectura, y un proceso de lectura haría esperar un escrito, y por supuesto las lecturas no tendrían que esperar el uno para el otro.
Además de las otras respuestas, el nivel de aislamiento de la transacción es importante, ya que las lecturas repetidas y serializadas son las que hacen que los bloqueos de 'lectura' se mantengan hasta el final de la transacción. Bloquear un recurso no causa un punto muerto. Mantenerlo cerrado lo hace. Las operaciones de escritura siempre mantienen sus recursos bloqueados hasta el final de la transacción.
Mi estrategia de prevención de bloqueo favorita es usar las funciones de 'instantánea'. La función de lectura de confirmación de lectura significa que las lecturas no usan bloqueos. Y si necesita más control que 'Lectura comprometida', existe la función 'Nivel de aislamiento de instantánea'. Este permite que se produzca una transacción serializada (usando los términos de MS aquí) sin bloquear a los otros jugadores.
Por último, se puede evitar una clase de puntos muertos utilizando un bloqueo de actualización. Si lee y mantiene la lectura (RETENER, o usa Lectura repetible), y otro proceso hace lo mismo, entonces ambos intentan actualizar los mismos registros, tendrá un punto muerto. Pero si ambos solicitan un bloqueo de actualización, el segundo proceso esperará al primero, mientras permite que otros procesos lean los datos usando bloqueos compartidos hasta que los datos se escriban realmente. Por supuesto, esto no funcionará si uno de los procesos aún solicita un bloqueo HOLD compartido.
fuente
Si bien los cursores son lentos en SQL Server, puede evitar el bloqueo en un cursor tirando de los datos de origen para el cursor en una tabla Temp y ejecutando el cursor sobre él. Esto evita que el cursor bloquee la tabla de datos real y los únicos bloqueos que obtienes son para las actualizaciones o inserciones realizadas dentro del cursor que solo se mantienen durante la duración de la inserción / actualización y no durante la duración del cursor.
fuente