Comment attraper SqlException causée par un blocage?

À partir d’une application .NET 3.5 / C #, je souhaiterais intercepter SqlException mais uniquement s’il est provoqué par des blocages sur une instance SQL Server 2008.

Un message d’erreur typique est que Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Cependant, cela ne semble pas être un code d’erreur documenté pour cette exception.

Filtrer les exceptions par rapport à la présence du mot-clé de blocage dans leur message semble être un moyen très laid d’obtenir ce comportement. Est-ce que quelqu’un connaît la bonne façon de le faire?

Le code d’erreur spécifique à Microsft SQL Server pour un interblocage est 1205, vous devez donc gérer l’exception SqlException et vérifier cela. Donc, par exemple, si pour tous les autres types de SqlException, vous voulez que la bulle soit l’exception:

 catch (SqlException ex) { if (ex.Number == 1205) { // Deadlock } else throw; } 

Ou, en utilisant le filtrage des exceptions disponible dans C # 6

 catch (SqlException ex) when (ex.Number == 1205) { // Deadlock } 

Une chose pratique à faire pour trouver le code d’erreur SQL réel pour un message donné, consiste à rechercher dans sys.messages dans SQL Server.

par exemple

 SELECT * FROM sys.messages WHERE text LIKE '%deadlock%' AND language_id=1033 

Une autre façon de gérer les blocages (à partir de SQL Server 2005 et versions ultérieures) consiste à le faire dans une procédure stockée à l’aide du support TRY … CATCH:

 BEGIN TRY -- some sql statements END TRY BEGIN CATCH IF (ERROR_NUMBER() = 1205) -- is a deadlock ELSE -- is not a deadlock END CATCH 

Il existe un exemple complet dans MSDN sur la manière de mettre en œuvre la logique de relance des impasses uniquement dans SQL.

Parce que je suppose que vous souhaitez éventuellement détecter les blocages, pour pouvoir réessayer l’opération ayant échoué, j’aimerais vous avertir d’un petit problème. J’espère que vous m’excuserez d’être un peu hors sujet ici.

Un blocage détecté par la firebase database annulera effectivement la transaction dans laquelle vous étiez en cours d’exécution (le cas échéant), tandis que la connexion rest ouverte dans .NET. Réessayer cette opération (dans cette même connexion) signifie qu’elle sera exécutée dans un contexte sans transaction, ce qui pourrait entraîner une corruption des données.

Il est important d’en être conscient. Il est préférable de considérer la connexion complète condamnée en cas de défaillance provoquée par SQL. Réessayer l’opération ne peut être effectué qu’au niveau où la transaction est définie (en recréant cette transaction et sa connexion).

Ainsi, lorsque vous réessayez une opération ayant échoué, veuillez vous assurer d’ouvrir une nouvelle connexion et de lancer une nouvelle transaction.

Voici un moyen C # 6 de détecter les impasses.

 try { //todo: Execute SQL. //IMPORTANT, if you used Connection.BeginTransaction(), this try..catch must surround that code. You must rollback the original transaction, then recreate it and re-run all the code. } catch (SqlException ex) when (ex.Number == 1205) { //todo: Retry SQL } 

Assurez-vous que ce try..catch entoure toute votre transaction. Selon @Steven (voir sa réponse pour plus de détails), lorsque la commande sql échoue à cause de l’interblocage, la transaction est annulée et, si vous ne recréez pas la transaction, votre tentative s’exécutera en dehors du contexte de la transaction et peut entraîner des incohérences de données.