2009-06-06 14 views
7

Ich habe ein Problem mit Deadlock auf SELECT/UPDATE auf SQL Server 2008. Ich las Antworten aus diesem Thread: SQL Server deadlocks between select/update or multiple selects, aber ich verstehe immer noch nicht, warum ich Deadlock bekomme.Deadlock auf SELECT/UPDATE

Ich habe die Situation im folgenden Testfall neu erstellt.

Ich habe eine Tabelle:

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 

Ich versuche zunächst einen Datensatz aus dieser Tabelle zu wählen und wenn der Datensatz vorhanden eingestellte Ablaufzeit auf aktuelle Zeit plus einem Intervall. Es wird mit folgendem Code erreicht:

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(); 
     } 
    } 
} 

Dann, wenn ich versuche, Testmethode von zwei Threads auszuführen und sie versuchen, denselben Datensatz ich folgende Ausgabe zu aktualisieren:

[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. 

Ich kann nicht verstehen, wie Es kann passieren, Serializable Isolation Level zu verwenden. Ich denke, zuerst sollte Zeile/Tabelle sperren und wird nicht zulassen, dass eine andere Sperren erhalten. Das Beispiel wird mit Befehlsobjekten geschrieben, aber es dient nur zu Testzwecken. Ursprünglich verwende ich linq aber ich wollte vereinfachtes Beispiel zeigen. Sql Server Profiler zeigt, dass Deadlock Key Lock ist. Ich werde die Frage in wenigen Minuten aktualisieren und Grafik von SQL Server Profiler nachzeichnen. Jede Hilfe wäre willkommen. Ich verstehe, dass die Lösung für dieses Problem kritischer Abschnitt im Code sein kann, aber ich versuche zu verstehen, warum Serializable Isolation Level nicht den Trick macht.

Und hier ist die Deadlock Graph: deadlock http://img7.imageshack.us/img7/9970/deadlock.gif

Vielen Dank im Voraus.

+0

+1 für eine gut dokumentierte Frage! –

Antwort

4

Es ist nicht genug, um eine serialisierbare Transaktion zu haben, Sie müssen die Sperre angeben, damit dies funktioniert.

Die serializable Isolationsstufe wird nach wie vor in der Regel erwerben die „schwächste“ Art der Sperre kann es die die serializable Bedingungen gewährleistet werden erfüllt (wiederholbar liest, keine Phantomzeilen usw.)

Also, Sie greifen eine gemeinsame Sperre auf Ihre Tabelle, die Sie später sind (in Ihrer serialisierbaren Transaktion) versuchen, auf an update lock. zu aktualisieren Die Aktualisierung wird fehlschlagen, wenn ein anderer Thread die gemeinsame Sperre hält (es funktioniert, wenn kein Körper sonst eine gemeinsame Sperre hält).

Sie wollen wahrscheinlich an folgende Änderungen:

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

dass eine Update-Sperre wird sichergestellt, erfasst, wenn die SELECT ausgeführt wird (so dass Sie nicht das Schloss aktualisieren müssen).

+0

Es hat funktioniert :) Ist es möglich, dies mit linq2sql zu erreichen? – empi

+0

Sieht aus wie keine ... http://StackOverflow.com/Questions/806775/Linq-to-sql-with-Updlock –

+0

Gestern habe ich versucht mit (REPEATABLEREAD), weil ich gelesen habe, dass dies entspricht SELECT UPDATE aber es ist hat nicht funktioniert. Wie gesagt, Updlock hat es geschafft. Ich denke, ich werde neue Frage zu updlock in linq2sql öffnen. Danke für die Antwort, es gab mir ernsthafte Kopfschmerzen;) – empi