2009-06-06 22 views
7

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.

+0

+1 za dobrze udokumentowane pytanie! –

Odpowiedz

4

To nie wystarczy, aby przeprowadzić transakcję, którą można serializować, aby wskazać blokadę, aby to działało.

Poziom izolacji serializable nadal będzie zazwyczaj nabywają „najsłabszy” typ zamka może on co zapewnia Serializable warunki są spełnione (powtarzalne czyta, nie wiersze fantomowe etc)

Tak, jesteś chwytając wspólną blokadę Twój stół, który będziesz później (w swojej transakcji, którą można serializować) próbuje uaktualnić do an update lock. Uaktualnienie nie powiedzie się, jeśli inny wątek będzie trzymał udostępnioną blokadę (zadziała, jeśli żaden inny organ nie będzie miał współużytkowanej blokady).

Prawdopodobnie chcesz go zmienić na następujące:

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

To zapewni blokadę aktualizacji nabywa się z chwilą SELECT jest wykonywana (więc nie trzeba będzie uaktualnić blokady).

+0

To działało :) Czy można to osiągnąć za pomocą linq2sql? – empi

+0

Wygląda jak nie ... http://stackoverflow.com/questions/806775/linq-to-sql-with-updlock –

+0

Wczoraj próbowałem z (REPEATABLEREAD), ponieważ przeczytałem, że jest to równoważne SELECT FOR UPDATE, ale to nie zadziałało. Jak już powiedziałem, updlock załatwił sprawę. Myślę, że otworzę nowe pytanie o updlock w linq2sql. Dziękuję za odpowiedź, To dało mi poważny ból głowy;) – empi