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.
+1 para una pregunta bien documentada! –