2010-11-10 36 views
5

Les journaux d'événements pour mon application .NET montrent qu'il se bloque parfois lors de la lecture de Sql Server. Ceci est généralement très rare car nous avons déjà optimisé nos requêtes pour éviter les blocages, mais ils se produisent parfois encore. Dans le passé, nous avons eu des interblocages qui se produisent lors de l'appel de la fonction ExecuteReader sur notre instance SqlCommand. Pour corriger cela, nous avons ajouté le code de nouvelle tentative pour exécuter simplement la requête à nouveau, comme ceci:Comment faire pour récupérer à partir de l'exception deadlock pendant SqlDataReader.Read()

//try to run the query five times if a deadlock happends 
int DeadLockRetry = 5; 

while (DeadLockRetry > 0) 
{ 
    try 
    { 
     return dbCommand.ExecuteReader(); 
    } 
    catch (SqlException err) 
    { 
     //throw an exception if the error is not a deadlock or the deadlock still exists after 5 tries 
     if (err.Number != 1205 || --DeadLockRetry == 0) 
      throw; 
    } 
} 

Cela a fonctionné très bien pour le cas où l'impasse est arrivé lors de la requête initiale exécuter, mais maintenant nous obtenons des interblocages en itérer à travers les résultats en utilisant la fonction Read() sur le SqlDataReader retourné.

Encore une fois, je ne suis pas préoccupé par l'optimisation de la requête, mais simplement essayer de récupérer dans le cas rare qu'un blocage se produit. Je pensais à utiliser un processus similaire. Je pourrais créer ma propre classe qui hérite de SqlDataReader qui remplace simplement la fonction Read avec le code de réessai. Comme ceci:

public class MyDataReader : SqlDataReader 
{ 
    public override bool Read() 
    { 
     int DeadLockRetry = 5; 
     while (DeadLockRetry > 0) 
     { 
      try 
      { 
       return base.Read(); 
      } 
      catch (SqlException ex) 
      { 
       if (ex.ErrorCode != 1205 || --DeadLockRetry == 0) 
        throw; 
      } 
     } 
     return false; 
    } 
} 

Est-ce la bonne approche? Je veux être sûr que les enregistrements ne sont pas ignorés dans le lecteur. Réessayer Read après un blocage sauter des lignes? De même, dois-je appeler Thread.Sleep entre les tentatives pour donner à la base de données le temps de sortir de l'état de blocage, ou est-ce suffisant. Ce cas n'est pas facile à reproduire, donc j'aimerais m'en assurer avant de modifier mon code.

EDIT:

Comme demandé, un peu plus d'informations sur ma situation: Dans un cas, j'ai un processus qui exécute une requête qui charge une liste des ids de disques qui ont besoin d'être mis à jour. Ensuite, je parcourir cette liste d'identifiants en utilisant la fonction Read et exécuter un processus de mise à jour sur cet enregistrement, qui finira par mettre à jour une valeur pour cet enregistrement dans la base de données. (Non, il n'y a aucun moyen d'effectuer la mise à jour dans la requête initiale, beaucoup d'autres choses se produisent pour chaque enregistrement qui est retourné). Ce code a bien fonctionné pendant un certain temps, mais nous utilisons un peu de code pour chaque enregistrement, donc je peux imaginer qu'un de ces processus crée un verrou sur la table initiale en cours de lecture. Après réflexion, la suggestion de Scottie d'utiliser une structure de données pour stocker les résultats résoudrait probablement cette situation. Je pourrais stocker les ids retournés dans un List<int> et la boucle à travers cela. De cette façon, les serrures sur les rangées peuvent être enlevées tout de suite.

Cependant, je serais toujours intéressé à savoir s'il existe un moyen général de récupérer des blocages sur une lecture.

+0

Pourriez-vous donner plus d'informations sur votre utilisation de DB? Il est étrange d'avoir une impasse en lisant des données. Cela signifie que votre code contient déjà des verrous pour d'autres données et qu'une autre transaction a verrouillé ce que vous essayez de lire et attend les données auxquelles vous avez déjà accédé. Pouvez-vous appeler ExecuteReader sur une autre transaction? – Timores

Répondre

4

Votre transaction entière est perdue dans un interblocage. Vous devez redémarrer à partir de zéro, de façon au-dessus du niveau où vous avez lu un lecteur de données. Vous dites que vous lisez des enregistrements, puis les mettez à jour en boucle.Vous devez redémarrer avec la lecture à nouveau les enregistrements:

function DoWork() { 
    using (TransactionScope scope = new TransactionScope(...)) { 
    cmd = new SqlCommand("select ..."); 
    using (DataReader rdr = cmd.ExecuteReader()) { 
     while(rdr.Read()) { 
      ... process each record 
     } 
    } 
    scope.Complete(); 
    } 
} 

vous avez pour recommencer toute DoWork appel:

retries = 0; 
success = false; 
do { 
try { 
    DoWork(); 
    success = true; 
} 
catch (SqlException e) { 
    if (can retry e) { 
    ++retries; 
    } 
    else { 
    throw; 
    } 
} 
} while (!success); 
+0

Ceci. Vous ne pouvez pas simplement essayer de relire, vous devez relancer la commande. Mais vous devriez certainement limiter le nombre de tentatives à 3-5 max, il faut du temps à SQL Server pour gérer les blocages. –

+0

Ok merci, c'est ce que je cherchais. Nous avons une couche de données abstraite qui renvoie IDataReaders pour toutes nos requêtes de lecture, puis notre code logique utilise les lecteurs. Donc, dans mon application, coder mes lectures comme vous l'avez suggéré n'est pas facile. J'espérais une solution générale, mais il semble que ce ne soit pas possible. Je vais envisager d'utiliser cette suggestion pour les cas spécifiques qui causent des blocages occasionnels. Merci à tous pour vos suggestions! – InvisibleBacon

0

Pourriez-vous envisager d'utiliser DataSets ou DataTables au lieu de DataReaders? Ma peur ici, bien que je ne sois pas positif, c'est que la lecture de l'erreur jette tout cet enregistrement. Vous pouvez tester cela dans un environnement contrôlé dans lequel vous pouvez forcer une erreur et voir si elle relit l'enregistrement ou s'il le supprime.

+0

Je suis à peu près sûr que l'utilisation d'un DataTable ou d'un ensemble aurait les mêmes vulnérabilités. Est-ce que .NET utiliserait Read en interne pour remplir le DataTable? Je voudrais vraiment être en mesure de tester cela dans un environnement contrôlé. Je ne sais pas exactement ce qui cause les blocages, donc ce serait difficile. – InvisibleBacon

+0

Vous n'avez pas besoin de tester l'interblocage en particulier. Si vous êtes dans un environnement contrôlé, vous pouvez arrêter le serveur SQL pendant une seconde et intercepter cette erreur. Cela vous dirait au moins si l'enregistrement est relu ou annulé. – Scottie

+0

Oui, les DataSets utilisent les mêmes mécanismes de lecture, mais je me demande si MS a déjà un code de blocage en place lors de la lecture? – Scottie