2009-06-06 13 views
7

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.

+0

+1 per una domanda ben documentata! –

risposta

4

Non è sufficiente disporre di una transazione serializzabile che è necessario suggerire sul blocco affinché funzioni.

Il livello di isolamento serializzabile sarà ancora di solito acquisire il tipo di blocco si può che assicura siano soddisfatte le condizioni serializzabili (ripetibile legge, nessuna riga fantasma, ecc)

Così, si stanno afferrando una condivisa Lock On "più deboli" la tua tabella che sei in seguito (nella tua transazione serializzabile) cercando di aggiornare a an update lock. L'aggiornamento fallirà se un altro thread è in possesso del blocco condiviso (funzionerà se nessun altro corpo è in possesso di un blocco condiviso).

probabilmente avrete bisogno di cambiare al seguente:

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

che garantiranno un blocco di aggiornamento è acquisito quando viene eseguita la SELECT (in modo che non sarà necessario aggiornare il blocco).

+0

Ha funzionato :) È possibile ottenere questo utilizzando linq2sql? – empi

+0

Sembra no ... http://stackoverflow.com/questions/806775/linq-to-sql-with-updlock –

+0

Ieri, ho provato con (REPEATABLEREAD) perché ho letto che questo è equivalente a SELECT FOR UPDATE ma non ha funzionato Come ho detto, l'aggiornamento ha fatto il trucco. Penso che aprirò una nuova domanda su updlock in linq2sql. Grazie per la risposta, mi stava causando un serio mal di testa;) – empi