Comment attraper SqlException causé par un blocage?

92

À partir d'une application .NET 3.5 / C #, je voudrais attraper, SqlExceptionmais uniquement si cela est causé par des blocages 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, cela ne semble pas être un code d'erreur documenté pour cette exception.

Le filtrage des exceptions contre la présence du mot-clé de blocage dans leur message semble une manière très moche de parvenir à ce comportement. Est-ce que quelqu'un connaît la bonne façon de procéder?

Joannes Vermorel
la source
3
J'ai (enfin) trouvé la documentation du 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
Martin McNulty

Réponses:

154

Le code d'erreur spécifique à Microsft SQL Server pour un blocage 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 augmente l'exception:

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

Ou, en utilisant le filtrage des exceptions disponible en 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é est de 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 supérieur) consiste à le faire dans une procédure stockée en utilisant le 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 ici dans MSDN de la façon d'implémenter la logique de relance de blocage uniquement dans SQL.

AdaTheDev
la source
2
Notez que les codes d'erreur sont spécifiques au fournisseur, donc 1205 est un blocage pour SQL Server, mais il peut être différent pour Oracle, MySQL, etc.
brianmearns
3
En fonction de la couche de données, le SqlExceptionpeut être enveloppé dans un autre. Nous pouvons donc avoir besoin d'attraper n'importe quel type d'exception et de les vérifier, puis, s'ils ne sont pas directement une exception de blocage, vérifiez récursivement leur InnerException.
Frédéric
46

Parce que je suppose que vous voulez peut-être détecter les blocages, pour pouvoir réessayer l'opération qui a échoué, j'aime vous avertir pour un petit coup. J'espère que vous m'excuserez d'être un peu hors sujet ici.

Un blocage détecté par la base de données annulera efficacement la transaction dans laquelle vous étiez en cours d'exécution (le cas échéant), tandis que la connexion est maintenue 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 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ète vouée à l'échec en cas d'échec causé par SQL. La relance de l'opération ne peut être effectuée 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 qui a échoué, assurez-vous d'ouvrir une toute nouvelle connexion et de démarrer une nouvelle transaction.

Steven
la source
4
Pourquoi avez-vous besoin d'une toute nouvelle connexion? J'ai publié une question sur cette réponse ici .
Sam
3

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

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 en raison du blocage, elle provoque l'annulation de la transaction et, si vous ne recréez pas la transaction, votre nouvelle tentative s'exécutera en dehors du contexte de la transaction et peut entraîner des incohérences dans les données.

Brian
la source