Sto riscontrando un problema con deadlock su SELECT/UPDATE su SQL Server 2008. Ho letto le risposte da questo thread: SQL Server deadlocks between select/update or multiple selects ma non riesco ancora a capire perché vengo bloccato.Deadlock su SELECT/UPDATE
Ho ricreato la situazione nel seguente banco di prova.
Ho una tabella:
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
sto cercando prima di selezionare un record da questa tabella e se il record esiste impostato ora di scadenza per ora corrente più un certo intervallo. Si realizza utilizzando il codice seguente:
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();
}
}
}
Poi se provo ad eseguire metodo di prova da due fili e cercano di aggiornare stesso record ottengo seguente output:
[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.
non riesco a capire come può succedere usando il livello di isolamento serializzabile. Penso che prima selezionare dovrebbe bloccare riga/tabella e non permetterà a un'altra selezione di ottenere alcun blocco. L'esempio è scritto usando gli oggetti comando ma è solo a scopo di test. Inizialmente, sto usando linq ma volevo mostrare un esempio semplificato. Sql Server Profiler mostra che deadlock è il blocco dei tasti. Aggiornerò la domanda in pochi minuti e posterò il grafico da SQL Server Profiler. Qualsiasi aiuto sarebbe apprezzato. Capisco che la soluzione per questo problema potrebbe essere la creazione di una sezione critica nel codice, ma sto cercando di capire perché il livello di isolamento serializzabile non faccia il trucco.
Ed ecco il grafico situazione di stallo: deadlock http://img7.imageshack.us/img7/9970/deadlock.gif
Grazie in anticipo.
+1 per una domanda ben documentata! –