Quelles sont les principales causes d'impasses et peuvent-elles être prévenues?

55

Récemment, une de nos applications ASP.NET a affiché une erreur de blocage de la base de données et il m'a été demandé de vérifier et de corriger l'erreur. J'ai réussi à trouver la cause du blocage était une procédure stockée qui mettait à jour de manière rigoureuse une table dans un curseur.

C'est la première fois que je vois cette erreur et que je ne sais pas comment la détecter et la corriger efficacement. J'ai essayé tous les moyens possibles que je connais, et j'ai finalement trouvé que la table en cours de mise à jour n'avait pas de clé primaire! Heureusement, c'était une colonne d'identité.

J'ai par la suite trouvé le développeur qui a scripté la base de données pour le déploiement en désordre. J'ai ajouté une clé primaire et le problème a été résolu.

Je me suis senti heureux et je suis revenu à mon projet et j'ai fait quelques recherches pour découvrir la raison de cette impasse ...

Apparemment, c'était une condition d'attente circulaire qui a provoqué l'impasse. Les mises à jour prennent apparemment plus de temps sans clé primaire qu'avec une clé primaire.

Je sais que ce n'est pas une conclusion bien définie, c'est pourquoi je poste ici ...

  • La clé primaire manquante est-elle le problème?
  • Existe-t-il d'autres conditions qui entraînent une impasse autres que (exclusion mutuelle, attente et maintien, absence de préemption et attente circulaire)?
  • Comment puis-je prévenir et suivre les blocages?
CoderHawk
la source
2
La majorité des IME (tous?) Des blocages que j'ai vus sont dus à des attentes circulaires (principalement à cause de l'utilisation abusive de déclencheurs).
Sathyajith Bhat le
La circularité est l'une des conditions nécessaires à une impasse. Vous pouvez éviter toute impasse si toutes vos sessions acquièrent des verrous dans le même ordre.
Peter G.

Réponses:

38

Le suivi des impasses est le plus simple des deux:

Par défaut, les blocages ne sont pas écrits dans le journal des erreurs. Vous pouvez faire en sorte que SQL écrive des impasses dans le journal des erreurs avec les indicateurs de trace 1204 et 3605.

Écrire des informations de blocage dans le journal des erreurs SQL Server: DBCC TRACEON (-1, 1204, 3605)

Désactivez-le: DBCC TRACEOFF (-1, 1204, 3605)

Reportez-vous à la section "Résolution des blocages" pour une discussion de l'indicateur de trace 1204 et de la sortie que vous obtiendrez lorsqu'il sera activé. https://msdn.microsoft.com/en-us/library/ms178104.aspx

La prévention est plus difficile, vous devez essentiellement rechercher les éléments suivants:

Le bloc de code 1 verrouille la ressource A, puis la ressource B, dans cet ordre.

Le bloc de code 2 verrouille la ressource B, puis la ressource A, dans cet ordre.

C'est la condition classique dans laquelle un blocage peut survenir. Si le verrouillage des deux ressources n'est pas atomique, le bloc de code 1 peut verrouiller A et être préempté, puis le bloc de code 2 se verrouille B avant que A ne récupère le temps de traitement. Maintenant, vous avez une impasse.

Pour éviter cette condition, vous pouvez effectuer les opérations suivantes:

Bloc de code A (code psuedo)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

Bloc de code B (pseudo code)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

sans oublier de déverrouiller A et B quand on en a fini avec eux

cela éviterait le blocage entre le bloc de code A et le bloc de code B

Du point de vue de la base de données, je ne sais pas trop comment éviter cette situation, car les verrous sont gérés par la base de données elle-même, c'est-à-dire que les verrous de ligne / table lors de la mise à jour des données. La plupart des problèmes que j'ai rencontrés sont ceux où vous avez vu le vôtre, dans un curseur. Les curseurs sont notoirement inefficaces, évitez-les si possible.

Glace noir
la source
Voulez-vous dire de verrouiller la ressource A avant la ressource B dans le bloc de code B? Comme cela a été écrit, cela entraînera des blocages… comme vous le dites vous-même dans les commentaires précédents. Autant que possible, vous souhaitez toujours verrouiller les ressources dans le même ordre, même si vous avez besoin de requêtes fictives au début pour garantir cet ordre de verrouillage.
Gerard ONeill
23

Mes articles préférés à lire et à apprendre sur les blocages sont les suivants: Conversation simple - Traquez les blocages et SQL Server Central - Utilisation de Profiler pour résoudre les blocages . Ils vous donneront des échantillons et des conseils sur la façon de gérer une situation difficile.

En bref, pour résoudre un problème actuel, j’allais raccourcir les transactions impliquées, en retirer la partie inutile, prendre en charge l’ordre dans lequel les objets sont utilisés, voir quel niveau d’isolement est réellement nécessaire, et non lire inutilement. Les données...

Mais mieux lire les articles, ils seront beaucoup plus agréables dans les conseils.

Marian
la source
16

Il est parfois possible de résoudre un blocage en ajoutant l'indexation, car cela permet à la base de données de verrouiller des enregistrements individuels plutôt que la table entière, ce qui permet de réduire les conflits et les risques de bourrage.

Par exemple, dans InnoDB :

Si vous n'avez pas d'index adapté à votre instruction et que MySQL doit analyser l'intégralité de la table pour traiter l'instruction, chaque ligne de la table est verrouillée, ce qui bloque toutes les insertions effectuées par d'autres utilisateurs dans la table. Il est important de créer de bons index afin que vos requêtes n'analysent pas inutilement de nombreuses lignes.

Une autre solution courante consiste à désactiver la cohérence transactionnelle lorsqu'elle n'est pas nécessaire ou à modifier autrement votre niveau d'isolation , par exemple un travail de longue durée pour calculer des statistiques ... une réponse précise suffit généralement, vous n'avez pas besoin de chiffres précis. comme ils changent de sous vous. Et si cela prend 30 minutes, vous ne voulez pas que toutes les autres transactions sur ces tables soient arrêtées.

...

En ce qui concerne leur suivi, cela dépend du logiciel de base de données que vous utilisez.

Joe
la source
Il est courant de laisser des commentaires lors du vote au noir ... C’est une réponse valable, une instruction select passant à un verrou de table et prenant pour toujours peut provoquer une impasse.
BlackICE
1
MS SQLServer peut également donner un comportement de verrouillage inattendu si les index ne sont pas en cluster. Il va silencieusement ignorer votre direction pour utiliser le verrouillage au niveau de la ligne et le fera au niveau de la page. Vous pouvez alors avoir des impasses en attente sur la page.
Jay
7

Juste pour développer le curseur. c'est vraiment très mauvais. Il verrouille toute la table puis traite les lignes une par une.

Il est préférable de parcourir les lignes à la manière d'un curseur à l'aide d'une boucle while

Dans la boucle while, une sélection sera effectuée pour chaque ligne de la boucle et le verrouillage ne se produira que sur une seule ligne à la fois. Le reste des données de la table est libre d'interrogation, ce qui réduit les risques de blocage.

En plus c'est plus rapide. On se demande pourquoi il y a des curseurs de toute façon.

Voici un exemple de ce type de structure:

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

Si votre champ d'identifiant est clairsemé, vous souhaiterez peut-être extraire une liste d'identifiants distinct et effectuer une itération:

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END
Nicolas de Fontenay
la source
6

Manquer une clé primaire n'est pas le problème. Au moins tout seul. Premièrement, vous n'avez pas besoin d'un primaire pour avoir des index. Deuxièmement, même si vous effectuez des analyses de table (ce qui doit se produire si votre requête n’utilise pas d’index, un verrou de table ne provoquera pas en soi un blocage. Un processus d’écriture attendrait une lecture et un processus de lecture attendre pour une écriture, et bien sûr, les lectures ne devraient pas attendre du tout.

En ajoutant aux autres réponses, le niveau d'isolation de transaction est important, car les lectures répétables et sérialisées sont la raison pour laquelle les verrous «en lecture» sont conservés jusqu'à la fin de la transaction. Verrouiller une ressource ne provoque pas un blocage. Le garder verrouillé fait. Les opérations d'écriture gardent toujours leurs ressources verrouillées jusqu'à la fin de la transaction.

Ma stratégie de prévention de verrouillage préférée utilise les fonctionnalités de "capture instantanée". La fonctionnalité Lire les instantanés validés signifie que les lectures n'utilisent pas de verrous! Et si vous avez besoin de plus de contrôle que "Lecture validée", il existe la fonction "Niveau d'isolation de capture instantanée". Celui-ci permet à une transaction sérialisée (en utilisant les termes MS ici) de se produire sans bloquer les autres joueurs.

Enfin, une classe de blocages peut être évitée en utilisant un verrou de mise à jour. Si vous lisez et maintenez la lecture (HOLD ou utilisez la lecture répétable) et qu'un autre processus fait de même, alors les deux essaient de mettre à jour les mêmes enregistrements, vous obtiendrez une impasse. Mais si les deux demandent un verrou de mise à jour, le second processus attendra le premier, tout en permettant aux autres processus de lire les données à l'aide de verrous partagés jusqu'à ce que les données soient réellement écrites. Bien entendu, cela ne fonctionnera pas si l’un des processus demande toujours un verrou HOLD partagé.

Gerard ONeill
la source
-2

Bien que les curseurs soient lents dans SQL Server, vous pouvez éviter les blocages de curseur en extrayant les données source du curseur dans une table Temp et en l'exécutant. Cela empêche le curseur de verrouiller la table de données réelle et les seuls verrous que vous obtenez concernent les mises à jour ou les insertions effectuées à l'intérieur du curseur qui ne sont maintenues que pendant la durée de l'insertion / mise à jour et non pour la durée du curseur.

Bill Joyce
la source