97 votes

Comment attraper SqlException causée par un deadlock?

À partir d'une application .NET 3.5 / C#, je voudrais attraper SqlException mais seulement s'il est causé par des deadlocks sur une instance SQL Server 2008.

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

Pourtant, il ne semble pas y avoir de code d'erreur documenté pour cette exception.

Filtrer les exceptions en fonction de la présence du mot-clé deadlock dans leur message semble être une façon très moche d'atteindre ce comportement. Est-ce que quelqu'un connaît la bonne méthode pour le faire?

3 votes

J'ai (enfin) trouvé la documentation pour le code d'erreur : msdn.microsoft.com/en-us/library/aa337376.aspx. Vous pouvez également le trouver via SQL Server lui-même : select * from master.dbo.sysmessages where error=1205

163voto

AdaTheDev Points 53358

Le code d'erreur spécifique au serveur Microsoft SQL Server pour un deadlock est 1205, donc vous devriez gérer l'exception SqlException et vérifier cela. Par exemple, si pour tous les autres types de SqlException vous voulez remonter l'exception :

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

Ou, en utilisant le filtrage d'exception disponible en C# 6

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

Une chose utile à faire pour trouver le code d'erreur SQL réel pour un message donné, est de regarder 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 deadlocks (à partir de SQL Server 2005 et au-delà), est de le faire dans une procédure stockée en utilisant la prise en charge de TRY...CATCH :

BEGIN TRY
    -- certaines déclarations SQL
END TRY
BEGIN CATCH
    IF (ERROR_NUMBER() = 1205)
        -- c'est un deadlock
    ELSE
        -- ce n'est pas un deadlock
END CATCH

Il y a un exemple complet ici dans MSDN de comment implémenter une logique de réessai de deadlock purement dans SQL.

2 votes

Notez que les codes d'erreur sont spécifiques au fournisseur, donc 1205 est un deadlock pour SQL Server, mais cela peut être différent pour Oracle, MySQL, etc.

3 votes

Selon la couche de données, la SqlException peut être enveloppée dans une autre. Nous devons donc attraper n'importe quel type d'exception et les vérifier, puis, s'ils ne sont pas directement une exception de blocage, vérifier récursivement leur InnerException.

48voto

Steven Points 56939

Parce que je suppose que vous voulez probablement détecter les impasses, afin de pouvoir réessayer l'opération échouée, j'aimerais vous prévenir d'un petit piège. J'espère que vous m'excuserez pour m'écarter un peu du sujet ici.

Une impasse détectée par la base de données annulera effectivement la transaction dans laquelle vous étiez en cours d'exécution (si c'est le cas), alors que la connexion est maintenue ouverte dans .NET. En réessayant cette opération (dans cette même connexion), cela signifie qu'elle sera exécutée dans un contexte sans transaction et cela 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ètement condamnée en cas d'échec causé par SQL. Réessayer l'opération ne peut être fait qu'au niveau où la transaction est définie (en recréant cette transaction et sa connexion).

Donc, lorsque vous réessayez une opération échouée, veuillez vous assurer d'ouvrir une nouvelle connexion totalement et de commencer une nouvelle transaction.

4 votes

Pourquoi avez-vous besoin d'une connexion complètement nouvelle? J'ai posté une question à propos de cette réponse ici.

7voto

Brian Points 13596

Voici une façon de détecter les impasses en C# 6.

try
{
    //todo: Exécutez SQL.
    //IMPORTANT, si vous avez utilisé Connection.BeginTransaction(), ce try..catch doit entourer ce code. Vous devez annuler la transaction d'origine, puis la recréer et réexécuter tout le code.
}
catch (SqlException ex) when (ex.Number == 1205)
{
    //todo: Réessayer SQL
}

Assurez-vous que ce try..catch entoure l'intégralité de votre transaction. Selon @Steven (consultez sa réponse pour plus de détails), lorsque la commande sql échoue en raison de l'impasse, cela entraîne l'annulation de la transaction et, si vous ne recréez pas la transaction, votre nouvelle tentative sera exécutée en dehors du contexte de la transaction et peut entraîner des incohérences de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X