Mam problem z zakleszczenia na SELECT/UPDATE na SQL Server 2008. Czytam odpowiedzi z tego wątku: SQL Server deadlocks between select/update or multiple selects, ale nadal nie rozumiem, dlaczego dostaję impasu.Zakleszczenie na SELECT/UPDATE
Odtworzyłem sytuację w następującej teście.
Mam tabeli:
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
próbuję najpierw wybrać rekord z tabeli i czy rekord istnieje zestaw do czasu wygaśnięcia aktualnego czasu oraz niektórych interwału. Jest to realizowane za pomocą następującego kodu:
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();
}
}
}
Następnie jeśli próbuję wykonać metodę testową z dwóch nitek i starają się aktualizować ten sam rekord dostaję następujący komunikat:
[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.
Nie mogę zrozumieć, jak może się zdarzyć, używając Serializable Isolation Level. Myślę, że najpierw wybierz powinien blokować wiersz/tabelę i nie pozwoli innym wybrać do uzyskania jakichkolwiek zamków. Przykład jest napisany przy użyciu obiektów poleceń, ale tylko w celach testowych. Pierwotnie używam linq, ale chciałem pokazać uproszczony przykład. Sql Server Profiler pokazuje, że zakleszczenie jest blokadą klucza. Uaktualnię pytanie w kilka minut i opublikuję wykres z profilera serwera SQL. Każda pomoc będzie doceniona. Rozumiem, że rozwiązaniem tego problemu może być tworzenie krytycznej sekcji kodu, ale próbuję zrozumieć, dlaczego Serializable Isolation Level nie działa.
A oto wykres zakleszczenia: deadlock http://img7.imageshack.us/img7/9970/deadlock.gif
góry dzięki.
+1 za dobrze udokumentowane pytanie! –