Pregunta

Tengo el siguiente gráfico de interbloqueo que describe dos sentencias SQL que se DEADLOCKING entre sí. No estoy seguro de cómo acaba de analizar este problema y luego arreglar mi código SQL para evitar que esto suceda.

gráfico de interbloqueo principal

texto alternativo http://img140.imageshack.us/img140/6193/deadlock1 .png Haga clic aquí para una imagen más grande.

El lado izquierdo, detalles

texto alternativo http://img715.imageshack.us/img715/3999/deadlock2 .png Haga clic aquí para una imagen más grande.

lado derecho, detalles

texto alternativo http://img686.imageshack.us/img686/5097/deadlock3 .png Haga clic aquí para una imagen más grande.

archivo XML sin punto muerto esquema

Haga clic aquí para descargar el archivo XML .

Esquema de tabla

texto alternativo http://img509.imageshack.us/img509/5843/deadlockschema .png

detalles

LogEntries tabla

texto alternativo http://img28.imageshack.us/img28/9732/deadlocklogentriestable .png

detalles

clientes conectados tabla

texto alternativo http://img11.imageshack.us/img11/7681/deadlockconnectedclient .png

¿Cuál es el código haciendo?

Estoy leyendo en una serie de archivos (por ejemplo. Digamos 3, para este ejemplo) al mismo tiempo . Cada archivo contiene datos diferentes pero el mismo tipo de datos. entonces insertar los datos en la tabla LogEntries y después (si es necesario) que insertar o eliminar algo de la mesa ConnectedClients.

Aquí está mi código SQL.

using (TransactionScope transactionScope = new TransactionScope())
{
    _logEntryRepository.InsertOrUpdate(logEntry);

    // Now, if this log entry was a NewConnection or an LostConnection, then we need to make sure we update the ConnectedClients.
    if (logEntry.EventType == EventType.NewConnection)
    {
        _connectedClientRepository.Insert(new ConnectedClient { LogEntryId = logEntry.LogEntryId });
     }

    // A (PB) BanKick does _NOT_ register a lost connection .. so we need to make sure we handle those scenario's as a LostConnection.
    if (logEntry.EventType == EventType.LostConnection ||
        logEntry.EventType == EventType.BanKick)
    {
        _connectedClientRepository.Delete(logEntry.ClientName, logEntry.ClientIpAndPort);
    }

    _unitOfWork.Commit();
    transactionScope.Complete();
}

Ahora cada archivo tiene su propia instancia UnitOfWork (lo que significa que tiene su propia conexión a la base de datos, transacciones y contexto repositorio). Así que estoy asumiendo que esto significa que hay 3 conexiones diferentes a la db todo está sucediendo al mismo tiempo.

Por último, esto es el uso de Entity Framework como repositorio, pero por favor, no deje que eso le impida tener un pensar en este problema .

Con una herramienta de perfilado, el Isolation Level es Serializable. También he intentado ReadCommited y ReadUncommited, pero los dos errores: -

  • ReadCommited: lo mismo que anteriormente. Callejón sin salida.
  • ReadUncommited: error diferente. EF excepción que dice que espera algún resultado de vuelta, pero no consiguió nada. Supongo que este es el LogEntryId Identidad (scope_identity) valor que se espera, pero no recupera debido a la lectura sucia.

Por favor, ayuda!

PS. Es SQL Server 2008, por cierto.


Actualización # 2

Después de leer Remus Ruşanu 's respuesta actualizada, sentí que podía tratar de proporcionar un poco más de información para ver si alguien más puede ayudar aún más.

EF Diagrama

texto alternativo http://img691.imageshack.us/img691/600/deadlockefmodel .png

Ahora, Remus sugiere (y la nota, él dice que está familiarizado con EF) ...

  

La última pieza del rompecabezas, la   nodo de bloqueo de la izquierda no explicada tiene en el   PK_ConnectedClients, asumiré que es   a partir de la puesta en práctica de EF   InsertOrUpdate. Probablemente hace una   las operaciones de búsqueda en primer lugar, y debido a la FK   relación entre declarado   ConnectedClients y LogEntries, se   busca en PK_ConnectedClients, por lo tanto,   la adquisición de la cerradura serializable.

Interesante. No estoy seguro de por qué el nodo de la izquierda tiene un bloqueo en PK_ConnectedClients, como se sugirió anteriormente. Ok, vamos a revisar el código para ese método ....

public void InsertOrUpdate(LogEntry logEntry)
{
    LoggingService.Debug("About to InsertOrUpdate a logEntry");

    logEntry.ThrowIfArgumentIsNull("logEntry");

    if (logEntry.LogEntryId <= 0)
    {
        LoggingService.Debug("Current logEntry instance doesn't have an Id. Instance object will be 'AddObject'.");
        Context.LogEntries.AddObject(logEntry);
    }
    else
    {
        LoggingService.Debug("Current logEntry instance has an Id. Instance object will be 'Attached'.");
        Context.LogEntries.Attach(logEntry);
    }
}

Hmm. se trata de una sencilla AddObject (aka. Insertar) o Attach (aka. Update). No hay referencias. código SQL tampoco indicio de cualquier material de consulta.

Ok entonces ... tengo otras dos métodos ... tal que están haciendo algunas operaciones de búsqueda?

En ConnectedClientRepository ...

public void Insert(ConnectedClient connectedClient)
{
    connectedClient.ThrowIfArgumentIsNull("connectedClient");

    Context.ConnectedClients.AddObject(connectedClient);
}

No -.> También básico, como

Lucky último método? Wow .. Ahora esto es interesante ....

public void Delete(string clientName, string clientIpAndPort)
{
    clientName.ThrowIfArgumentIsNullOrEmpty("clientName");
    clientIpAndPort.ThrowIfArgumentIsNullOrEmpty("clientIpAndPort");

    // First we need to attach this object to the object manager.
    var existingConnectedClient = (from x in GetConnectedClients()
                                   where x.LogEntry.ClientName == clientName.Trim() &&
                                   x.LogEntry.ClientIpAndPort == clientIpAndPort.Trim() &&
                                   x.LogEntry.EventTypeId == (byte)EventType.NewConnection
                                   select x)
                                  .Take(1)
                                  .SingleOrDefault();

    if (existingConnectedClient != null)
    {
        Context.ConnectedClients.DeleteObject(existingConnectedClient);
    }
}

Así que, mirando por encima, agarro una instancia del disco que desea borrar .. y si existe, a continuación, elimínelo.

Así que .. Si comento que llamada a un método, en mi lógica inicial hasta llegar a la parte superior de este post ... SO lo que sucede?

funciona. WOWZ.

También funciona bien como Serializable o Read Commited - tanto en el trabajo cuando no llamo el método Delete

.

¿Por qué entonces que borrar método será conseguir un bloqueo? ¿Es Becuase la selección (con serializable) tiene una cerradura y un poco de estancamiento ocurre?

Con read committed, es posible que tengo 3 llamadas a la eliminación ocurren al mismo tiempo.

  • primero agarra una instancia de los datos.
  • segunda (y tercera) toma otra instancia de los mismos datos.
  • Ahora, primero eliminar de. bien.
  • segunda borra .. pero la fila ha ido .. así que por lo tanto, me sale ese error extraño en ve afectado un número inesperado de filas (0). <== cero los elementos eliminados.

Posible? Si es así .. er ... ¿cómo puedo solucionar este problema? Es este un caso clásico de una condición de carrera? ¿Es posible evitar que esto de alguna manera happeneing?


Actualizaciones

  • Se han solucionado los enlaces a las imágenes.
  • Enlace al archivo XML sin procesar estancamiento. Aquí es el mismo enlace .
  • Añadido base de datos de tabla de esquema.
  • Añadido ambas detalles de la tabla.
¿Fue útil?

Solución

El nodo lado izquierdo está celebrando una RangeS-U lock en PK_CustomerRecords y quiere un bloqueo RangeS-U en i1 (supongo que su un índice en LogEntries). El nodo lado derecho tiene un bloqueo en RangeS-U i1 y quiere una RangeI-N en PK_CustomerRecords.

Al parecer, el interbloqueo se produce entre el _logEntriesRepository.InsertOrUpdate (nodo izquierdo) y _connectedClientRepository.Insert (nodo derecha). Sin conocer el tipo de relaciones declaradas EF, que no puedo comentar sobre por qué el nodo lado izquierdo tiene un bloqueo en PK_CustomerRecords en el momento en que se inserta la LogEntry. Sospecho que esto es causado por un comportamiento de tipo ORM inducida por EF, como las operaciones de búsqueda de un miembro de 'precargado', o puede ser causado por una TransactionScope nivel más alto que rodea el alcance en el código cortó publicada.

Como otros han dicho, es necesario publicar el esquema de la base en una evaluación de punto muerto, ya que la ruta de acceso (índices utilizados) es crítica. Véase mi artículo de lectura-escritura estancamiento para una discusión más detallada sobre la implicación de índices en los puntos muertos.

Mi primera recomendación sería la de obligar al ámbito de transacción para ser read committed. El nivel serializable por defecto de TransactionScopes casi nunca es necesario en la práctica, es un cerdo de rendimiento, y en este caso particular anuncios mucho ruido innecesario a la investigación estancamiento llevando los bloqueos de rango en la ecuación, lo que complica todo. Por favor, publicar la información de estancamiento que se produce en virtud de lectura confirmada.

Además, no publicar una imagen del gráfico de interbloqueo. Una imagen vale más que mil palabras, no es cierto en este caso, registrar el XML estancamiento. ORIGINAL: tiene una gran cantidad de información no visible en las imágenes bonitas

Actualizar

Desde el punto muerto XML que se puede ver que el nodo izquierdo está ejecutando insert [dbo].[LogEntries]([GameFileId], [CreatedOn], [EventTypeId], [Message], [Code], [Violation], [ClientName], [ClientGuid], [ClientIpAndPort]) values (@0, @1, @2, null, null, null, @3, @4, @5) (el elemento <executionStack><frame>). Pero lo más importante, puedo ver el objeto detrás del índice misterioso 'i1': objectname="AWing.sys.fulltext_index_docidstatus_1755869322" indexname="i1". Por lo que el interbloqueo se produce en un índice de texto completo .

Así que la explicación completa de estancamiento es:

  • nodo derecho está en _connectedClientRepository.Insert, se necesita un bloqueo inserto gama en PK_ConnectedClients. Tiene una cerradura rangos de U en el i1 índice de texto completo de la ejecución de _logEntryRepository.InsertOrUpdate anteriormente.
  • nodo de la izquierda está en el _logEntryRepository.InsertOrUpdate, en la sentencia INSERT dentro del lote, y se necesita un bloqueo de rangos de U en el i1 índice de texto completo. Tiene una cerradura rangos-S en PK_ConnectedClients que bloquea el nodo correcto, y esto no se explica por nada en el XML gráfico.

La última pieza del rompecabezas, el bloqueo inexplicable dejó nodo tiene sobre los PK_ConnectedClients, asumiré es de la aplicación de EF InsertOrUpdate. Es probable que realiza una búsqueda en primer lugar, y debido a la relación entre el FK declarada ConnectedClients y LogEntries, se busca en PK_ConnectedClients, por lo tanto, la adquisición de la cerradura serializable.

El principal culpable aquí son el nivel de aislamiento (Serializable) y el comportamiento EF en InsertOrUpdate. No puedo dar consejos sobre el comportamiento EF, pero el nivel serializable es un exceso de seguro. Lo que nos lleva de vuelta al error que se obtiene bajo nivel de lectura confirmada, que, por desgracia, es de nuevo un error EF no puedo comentar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top