2009-06-06 18 views
7

Tengo un problema con interbloqueo en SELECCIONAR/ACTUALIZAR en SQL Server 2008. He leído las respuestas de este hilo: SQL Server deadlocks between select/update or multiple selects pero todavía no entiendo por qué tengo un punto muerto.Interbloqueo en SELECCIONAR/ACTUALIZAR

He vuelto a crear la situación en el siguiente caso de prueba.

Tengo una tabla:

CREATE TABLE [dbo].[SessionTest](
    [SessionId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL, 
    [ExpirationTime] DATETIME NOT NULL, 
    CONSTRAINT [PK_SessionTest] PRIMARY KEY CLUSTERED (
     [SessionId] ASC 
    ) WITH (
     PAD_INDEX = OFF, 
     STATISTICS_NORECOMPUTE = OFF, 
     IGNORE_DUP_KEY = OFF, 
     ALLOW_ROW_LOCKS = ON, 
     ALLOW_PAGE_LOCKS = ON 
    ) ON [PRIMARY] 
) ON [PRIMARY] 
GO 

ALTER TABLE [dbo].[SessionTest] 
    ADD CONSTRAINT [DF_SessionTest_SessionId] 
    DEFAULT (NEWID()) FOR [SessionId] 
GO 

Estoy tratando primero para seleccionar un registro de esta tabla y si el registro existe conjunto tiempo de caducidad a la hora más algún intervalo. Esto se logra utilizando el código siguiente:

protected Guid? GetSessionById(Guid sessionId, SqlConnection connection, SqlTransaction transaction) 
{ 
    Logger.LogInfo("Getting session by id"); 
    using (SqlCommand command = new SqlCommand()) 
    { 
     command.CommandText = "SELECT * FROM SessionTest WHERE SessionId = @SessionId"; 
     command.Connection = connection; 
     command.Transaction = transaction; 
     command.Parameters.Add(new SqlParameter("@SessionId", sessionId)); 

     using (SqlDataReader reader = command.ExecuteReader()) 
     { 
      if (reader.Read()) 
      { 
       Logger.LogInfo("Got it"); 
       return (Guid)reader["SessionId"]; 
      } 
      else 
      { 
       return null; 
      } 
     } 
    } 
} 

protected int UpdateSession(Guid sessionId, SqlConnection connection, SqlTransaction transaction) 
{ 
    Logger.LogInfo("Updating session"); 
    using (SqlCommand command = new SqlCommand()) 
    { 
     command.CommandText = "UPDATE SessionTest SET ExpirationTime = @ExpirationTime WHERE SessionId = @SessionId"; 
     command.Connection = connection; 
     command.Transaction = transaction; 
     command.Parameters.Add(new SqlParameter("@ExpirationTime", DateTime.Now.AddMinutes(20))); 
     command.Parameters.Add(new SqlParameter("@SessionId", sessionId)); 
     int result = command.ExecuteNonQuery(); 
     Logger.LogInfo("Updated"); 
     return result; 
    } 
} 

public void UpdateSessionTest(Guid sessionId) 
{ 
    using (SqlConnection connection = GetConnection()) 
    { 
     using (SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) 
     { 
      if (GetSessionById(sessionId, connection, transaction) != null) 
      { 
       Thread.Sleep(1000); 
       UpdateSession(sessionId, connection, transaction); 
      } 
      transaction.Commit(); 
     } 
    } 
} 

Entonces, si intento ejecutar el método de ensayo de dos hilos y tratan de actualizar mismo registro consigo siguiente resultado:

[4] : Creating/updating session 
[3] : Creating/updating session 
[3] : Getting session by id 
[3] : Got it 
[4] : Getting session by id 
[4] : Got it 
[3] : Updating session 
[4] : Updating session 
[3] : Updated 
[4] : Exception: Transaction (Process ID 59) was deadlocked 
on lock resources with another process and has been 
chosen as the deadlock victim. Rerun the transaction. 

No puedo entender cómo puede suceder usando el nivel de aislamiento serializable. Creo que la primera selección debe bloquear la fila/tabla y no permitirá que otra seleccione para obtener bloqueos. El ejemplo está escrito usando objetos de comando pero solo para fines de prueba. Originalmente, estoy usando linq pero quería mostrar un ejemplo simplificado. Sql Server Profiler muestra que el punto muerto es el bloqueo de teclas. Actualizaré la pregunta en unos minutos y publicaré un gráfico desde el servidor de perfiles sql. Cualquier ayuda sería apreciada. Entiendo que la solución para este problema puede estar creando una sección crítica en el código, pero estoy tratando de entender por qué el nivel de aislamiento serial no funciona.

Y aquí está el gráfico de interbloqueo: deadlock http://img7.imageshack.us/img7/9970/deadlock.gif

Gracias de antemano.

+0

+1 para una pregunta bien documentada! –

Respuesta

4

No es suficiente tener una transacción serializable que necesita insinuar el bloqueo para que esto funcione.

El nivel de aislamiento serializable seguirá siendo por lo general la adquisición del tipo "más débil" de bloqueo que puede que asegura que se cumplan las condiciones serializables (lee repetible, no hay filas fantasma etc)

Por lo tanto, se están agarrando una compartida bloqueo en su tabla que está más tarde (en su transacción serializable) tratando de actualizar a an update lock. La actualización fallará si otro hilo mantiene el bloqueo compartido (funcionará si no hay otro elemento que tenga un bloqueo compartido).

es probable que desee cambiar a la siguiente:

SELECT * FROM SessionTest with (updlock) WHERE SessionId = @SessionId 

que asegurará un bloqueo de actualización se adquiere cuando se realiza la SELECT (por lo que no tendrá que actualizar la cerradura).

+0

Funcionó :) ¿Es posible lograr esto usando linq2sql? – empi

+0

Parece que no ... http://stackoverflow.com/questions/806775/linq-to-sql-with-updlock –

+0

Ayer, probé con (REPEATABLEREAD) porque leí que esto es equivalente a SELECCIONAR PARA ACTUALIZAR pero no funcionó Como dije updlock hizo el truco. Creo que abriré una nueva pregunta sobre el desbloqueo en linq2sql. Gracias por la respuesta, me estaba dando un gran dolor de cabeza;) – empi

Cuestiones relacionadas