2009-06-06 11 views
7

J'ai un problème de blocage sur SELECT/UPDATE sur SQL Server 2008. J'ai lu les réponses de ce sujet: SQL Server deadlocks between select/update or multiple selects mais je ne comprends toujours pas pourquoi je me retrouve dans une impasse.Deadlock sur SELECT/UPDATE

J'ai recréé la situation dans le cas de test suivant.

J'ai une table:

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 

J'essaie d'abord sélectionner un enregistrement de cette table et si l'enregistrement existe délai d'expiration à l'heure, plus un certain intervalle. Il est réalisé en utilisant le code suivant:

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

Ensuite, si je tente d'exécuter la méthode d'essai de deux fils et ils essaient de mettre à jour même enregistrement je suis sortie suivante:

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

Je ne peux pas comprendre comment cela peut arriver en utilisant le niveau d'isolement sérialisable. Je pense que le premier choix devrait verrouiller la ligne/table et ne laissera pas un autre sélectionner pour obtenir des verrous. L'exemple est écrit à l'aide d'objets de commande mais uniquement à des fins de test. A l'origine, j'utilise linq mais je voulais montrer un exemple simplifié. Sql Server Profiler montre que le blocage est le verrouillage des touches. Je vais mettre à jour la question dans quelques minutes et poster un graphique à partir du profileur de SQL Server. Toute aide serait appréciée. Je comprends que la solution pour ce problème peut être de créer une section critique dans le code, mais j'essaie de comprendre pourquoi le niveau d'isolement sérialisable ne fait pas l'affaire.

Et voici le graphique de blocage: deadlock http://img7.imageshack.us/img7/9970/deadlock.gif

Merci à l'avance.

+0

+1 pour une question bien documentée! –

Répondre

4

Il ne suffit pas d'avoir une transaction sérialisable, vous devez faire allusion au verrouillage pour que cela fonctionne.

Le niveau d'isolation sérialisable va acquérir encore généralement le type de serrure « faible » il peut qui assure les conditions sérialisables sont remplies (lectures répétables, aucune ligne de fantôme etc)

Alors, vous êtes Récolter un verrou partagé sur votre table que vous êtes plus tard (dans votre transaction sérialisable) essayant de passer à an update lock. La mise à niveau échouera si un autre thread maintient le verrou partagé (cela fonctionnera si aucun autre corps ne détient un verrou partagé).

Vous voulez probablement changer à ce qui suit:

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

qui assurera un verrou de mise à jour est acquise lorsque le SELECT est effectuée (vous ne aurez pas besoin de mettre à niveau la serrure).

+0

Cela a fonctionné :) Est-il possible d'y parvenir en utilisant linq2sql? – empi

+0

On dirait que non ... http://stackoverflow.com/questions/806775/linq-to-sql-with-updlock –

+0

Hier, j'ai essayé avec (REPEATABLEREAD) parce que j'ai lu que c'est équivalent à SELECT FOR UPDATE mais n'a pas travaillé. Comme je l'ai dit, updlock a fait l'affaire. Je pense que je vais ouvrir une nouvelle question à propos de updlock dans linq2sql. Merci pour la réponse, Il me donnait mal à la tête sérieux;) – empi

Questions connexes